Skip to main content

Withdrawals


//! # Unit Withdraw Pallet
//! <!-- Original author of paragraph: @gang
//!
//! ## Overview
//!
//! Pallet that allows a user to withdraw tokens to the native blockchain.
//!
//! ### Goals
//!
//! The pallet is designed to make the following possible:
//!
//! * Create a withdraw.
//! * Cancel a withdraw.
//! * Execute a withdraw.
//! * Accept a withdraw.
//! * Register a worker.
//! * Unregister a worker.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! - `withdraw`: Withdraw token to native blockchain.
//! - `cancel_withdraw`: Cancels a withdraw.
//! - `execute_withdraw`: Executes a withdraw if its pending.
//! - `cancel_execute_withdraw`: Cancel the execution of a withdraw if the origin is the one
//! processing this withdraw.
//! - `accept_withdraw`: Allows a worker to accept a withdraw, authorizing it to be processed.
//! - `register_worker`: Permits an account ID to be registered as a worker.
//! - `unregister_worker`: Permits an account ID to be unregistered as a worker.
//!
//!//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
//!
//! ### Change history
//!
//! 10/04/2023 - Walquer Valles - Added Benchmaks

#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/reference/frame-pallets/>
pub use pallet::*;
use sp_std::{prelude::*, vec};
mod types;
pub use types::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

pub mod weights;
pub use weights::WeightInfo;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

// Type for withdraw creation
pub type WithdrawCreationOf<T> = Withdraw<
<T as frame_system::Config>::AccountId,
<T as pallet::Config>::AssetId,
<T as pallet::Config>::AssetBalance,
WithdrawId,
<T as pallet::Config>::StringLimit,
>;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::*,
traits::{
fungibles,
tokens::{
fungibles::{metadata::Inspect as InspectMetadata, Inspect, Mutate},
Balance,
Fortitude::Polite,
Precision::Exact,
},
UnixTime,
},
};
use frame_system::pallet_prelude::*;

use pallet_exchange::WorkersRewards;

use sp_runtime::{
traits::{CheckedAdd, CheckedDiv, Hash, SaturatedConversion, Zero},
ArithmeticError, FixedPointOperand, Perbill, Saturating,
};
use sp_std::vec::Vec;
use traits::{
asset::{AssetInterface, TokenType},
oracle::OracleInterface,
subaccounts::{AccountOrigin, SubAccounts, SubAccountsAddress},
treasury::TreasuryInterface,
};

/// Balance type alias.
pub type BalanceOf<T> = <T as Config>::AssetBalance;

/// Asset id type alias.
pub type AssetId<T> = <T as Config>::AssetId;

/// Account id type alias.
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;

/// Type used to represent the deposit id.
pub type WithdrawId = u64;

/// Type used to represent the token decimals.
pub type Decimals = u8;

#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

type AssetId: Member
+ Parameter
+ Copy
+ MaybeSerializeDeserialize
+ MaxEncodedLen
+ Default
+ Zero
+ From<u32>;

/// The data supply type
type OracleSupply: Parameter
+ Member
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ From<u128>;

/// Type to access the Assets Pallet.
type Fungibles: fungibles::Inspect<
Self::AccountId,
AssetId = Self::AssetId,
Balance = Self::AssetBalance,
> + fungibles::Mutate<Self::AccountId>
+ InspectMetadata<Self::AccountId>
+ AssetInterface<Self::AssetId, Self::OracleSupply, Self::AccountId, Decimals, TokenType>;

type AssetBalance: Balance
+ FixedPointOperand
+ MaxEncodedLen
+ MaybeSerializeDeserialize
+ TypeInfo
+ From<u128>;

/// Type to access the sub account pallet.
type SubAccounts: SubAccounts<Self::AccountId, AccountOrigin>
+ SubAccountsAddress<AssetId<Self>, Self::AccountId, Self::Hash>;

/// Type to access the Oracle Pallet interface.
type OracleInterface: OracleInterface<
<Self::Fungibles as Inspect<Self::AccountId>>::AssetId,
<Self::Fungibles as Inspect<Self::AccountId>>::Balance,
Decimals,
>;

/// Helper type to hold traits for benchmarks.
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper;

/// Weight Information for extrinsics in this pallet.
type WeightInfo: WeightInfo;

#[pallet::constant]
type ProfileStringLimit: Get<u32>;

// Provides actual time
type TimeProvider: UnixTime;

/// Type to access the treasury pallet
type Treasury: TreasuryInterface<AssetId<Self>, BalanceOf<Self>, Self::AccountId>;

/// The maximum length of strings.
#[pallet::constant]
type StringLimit: Get<u32>;

#[pallet::constant]
type BitcoinAssetId: Get<AssetId<Self>>;

#[pallet::constant]
type EthereumAssetId: Get<AssetId<Self>>;

#[pallet::constant]
type NearAssetId: Get<AssetId<Self>>;

#[pallet::constant]
type PolygonAssetId: Get<AssetId<Self>>;

#[pallet::constant]
type AddressLengthLimit: Get<u32>;

#[pallet::constant]
type VotingPeriodAfterConsensus: Get<BlockNumberFor<Self>>;

type WorkersRewards: WorkersRewards<AssetId<Self>, Self::AccountId, BalanceOf<Self>>;

/// UNIT asset id.
#[pallet::constant]
type UnitAssetId: Get<AssetId<Self>>;

/// UNIT asset id.
#[pallet::constant]
type UsduId: Get<AssetId<Self>>;
}

/// User withdraw ids
#[pallet::storage]
#[pallet::getter(fn user_withdraw_id)]
pub type UserWithdrawsId<T: Config> =
StorageMap<_, Twox64Concat, AccountIdOf<T>, Vec<WithdrawId>, ValueQuery>;

/// Withdraw id
#[pallet::storage]
#[pallet::getter(fn withdraw_id_counter)]
pub type WithdrawIdCounter<T: Config> = StorageValue<_, WithdrawId, ValueQuery>;

/// Store pending transactions.
/// deposit_id -> option<bool>
#[pallet::storage]
#[pallet::getter(fn get_pending_transactions)]
pub type PendingTransactions<T: Config> =
StorageMap<_, Blake2_128Concat, WithdrawId, bool, OptionQuery>;

/// Store transactions hashs processed.
/// hash -> option<bool>
#[pallet::storage]
#[pallet::getter(fn get_processed_transaction_hash)]
pub type ProcessedTransactionsHash<T: Config> =
StorageMap<_, Blake2_128Concat, T::Hash, bool, OptionQuery>;

/// Store the WithdrawExecution structs.
/// withdraw_id -> option<WithdrawExecution>
#[pallet::storage]
#[pallet::getter(fn get_withdraw_execution)]
pub type WithdrawExecutionRequests<T: Config> =
StorageMap<_, Blake2_128Concat, WithdrawId, WithdrawExecution<AccountIdOf<T>>, OptionQuery>;

/// Pool worker id
#[pallet::storage]
#[pallet::getter(fn pool_worker_id)]
pub type PoolWorkerId<T: Config> = StorageValue<_, AccountIdOf<T>, OptionQuery>;

/// Store the transaction being executed.
/// deposit_id -> option<bool>
#[pallet::storage]
#[pallet::getter(fn get_executing_transactions)]
pub type ExecutingTransactions<T: Config> =
StorageMap<_, Blake2_128Concat, WithdrawId, bool, OptionQuery>;

/// Mapping Withdraw id to Withdraw
#[pallet::storage]
#[pallet::getter(fn withdraw_id_to_withdraw)]
pub type WithdrawIdToWithdraw<T: Config> = StorageMap<
_,
Twox64Concat,
WithdrawId,
Withdraw<AccountIdOf<T>, AssetId<T>, BalanceOf<T>, WithdrawId, T::StringLimit>,
>;

#[pallet::storage]
#[pallet::getter(fn nonce)]
pub type Nonce<T: Config> = StorageValue<_, u32, ValueQuery>;

// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/main-docs/build/events-errors/
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A `withdraw` was created.
CreatedWithdraw {
who: AccountIdOf<T>,
withdraw_id: WithdrawId,
asset_id: AssetId<T>,
amount: BalanceOf<T>,
to: BoundedVec<u8, T::StringLimit>,
},
/// A `withdraw` was cancelled.
CancelledWithdraw { who: AccountIdOf<T>, withdraw_id: WithdrawId },
/// A 'withdraw' is being executed.
ExecutingWithdraw { who: AccountIdOf<T>, withdraw_id: WithdrawId },
/// A 'withdraw' is not being executed anymore.
CancellingExecuteWithdraw { who: AccountIdOf<T>, withdraw_id: WithdrawId },
/// A `withdraw` was accepted.
WithdrawAccepted { withdraw_id: WithdrawId },
/// A `withdraw` was declined.
WithdrawDeclined { withdraw_id: WithdrawId },
/// A `worker` was registered.
WorkerRegistered { who: AccountIdOf<T> },
/// A `worker` was unregistered.
WorkerUnregistered { who: AccountIdOf<T> },
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
/// Not AssetId Provides
NoAssetId,
/// No balance to withdraw
NotEnoughAssetBalance,
/// No withdraw ID,
NoWithdrawId,
/// Withdraw Id dose not match
WithdrawIdNotMatch,
/// No permission to accept/decline a withdrawal request or cancel a withdrawal execution.
NoPermission,
/// Only pending withdraws can be cancelled.
WithdrawNotPending,
/// Only executing withdraws can be accepted.
WithdrawNotExecuting,
/// The withdraw is already being executed by a user.
WithdrawAlreadyBeingExecuted,
/// The withdraw is not being executed by a user.
WithdrawNotBeingExecuted,
/// The transaction is already processed.
TransactionAlreadyProcessed,
/// The account is already a registered as a deposit worker.
WorkerAlreadyRegistered,
/// The account is not registered as a deposit worker.
WorkerIsNotRegistered,
/// The address is not the same in withdraw executor.
AddressNotExecutor,
/// Withdraw already cancelled
WithdrawAlreadyCancelled,
/// The signature verification failed
VerificationFailed,
/// The signature has invalid format
InvalidSignedMessage,
/// The public key has invalid format
InvalidPublicKey,
/// The asset doesn't support the signature verification mechanism
UnprotectedAsset,
/// The asset supports the signature verification mechanism
ProtectedAsset,
/// Overflow of integer
Overflow,
/// Underflow of integer
Underflow,
/// No pool ID initialized
NoPoolId,
/// Only tokens of type WrappedCrypto are withdrawable.
OnlyWrappedCryptoIsWithdrawable,
/// No oracle has been provided
NoOracleProvided,
}

// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call(weight(<T as Config>::WeightInfo))]
impl<T: Config> Pallet<T> {
/// The origin can request a withdraw of a token to the native blockchain.
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
/// - `amount`: Amount of token.
/// - `to`: Native blockchain address.
/// - `signed_message`: Signature
///
/// Emits `CreatedWithdraw` event when successful.
#[pallet::call_index(0)]
pub fn withdraw(
origin: OriginFor<T>,
asset_id: AssetId<T>,
amount: BalanceOf<T>,
to: BoundedVec<u8, T::StringLimit>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;

ensure!(
<<T as pallet::Config>::Fungibles as Inspect<T::AccountId>>::asset_exists(asset_id),
Error::<T>::NoAssetId
);

ensure!(
<<T as pallet::Config>::Fungibles as AssetInterface<
AssetId<T>,
T::OracleSupply,
T::AccountId,
Decimals,
TokenType,
>>::is_wrapped_crypto(asset_id),
Error::<T>::OnlyWrappedCryptoIsWithdrawable
);

let address_hash = T::Hashing::hash(&to);
T::SubAccounts::is_account_address_owner(who.clone(), asset_id, address_hash)?;

ensure!(
T::Fungibles::balance(asset_id, &who) >= amount,
Error::<T>::NotEnoughAssetBalance
);

let withdraw_id = WithdrawIdCounter::<T>::get();

// Adding withdraw id to user withdraws vector
UserWithdrawsId::<T>::mutate(who.clone(), |val| -> DispatchResult {
val.push(withdraw_id);
Ok(())
})?;

// Generate a pseudo-random number to create a unique amount in order to distringuish
// the transaction in the assets original chain
let current_block_number = <frame_system::Pallet<T>>::block_number();
let block_hash = <frame_system::Pallet<T>>::block_hash(current_block_number);
let pseudo_random_value = T::Hashing::hash_of(&block_hash);
let bytes = pseudo_random_value.as_ref();
let decimal_random_number: u128;

let nonce = Nonce::<T>::get();

// Use a Nonce to diffenciate two same amount deposited in the same block
let random_number: u32 = (bytes[0] as u32)
.checked_add(bytes[1] as u32)
.ok_or(Error::<T>::Overflow)?
.checked_add(bytes[2] as u32)
.ok_or(Error::<T>::Overflow)?
.checked_add(nonce)
.ok_or(Error::<T>::Overflow)? %
1000;

// Start to 0 again whenever the u32 reach its limit
Nonce::<T>::put(nonce.wrapping_add(1));

// Get token decimals
let token_decimals = T::Fungibles::decimals(asset_id);
let precision_factor =
(10u128).checked_pow(token_decimals as u32).ok_or(Error::<T>::Overflow)?;

if asset_id == T::BitcoinAssetId::get() || asset_id == T::EthereumAssetId::get() {
// Target the 7, 8 and 9 decimals
decimal_random_number = (random_number as u128)
.checked_mul(
precision_factor
.checked_div(10000000000_u128)
.ok_or(Error::<T>::Underflow)?,
)
.ok_or(Error::<T>::Overflow)?;
} else {
// Target the 3, 4 and 5 decimals
decimal_random_number = (random_number as u128)
.checked_mul(
precision_factor.checked_div(1000000_u128).ok_or(Error::<T>::Underflow)?,
)
.ok_or(Error::<T>::Overflow)?;
}

let unique_amount =
amount.saturating_sub(Self::u128_to_asset_balance(decimal_random_number));

// The one that accept to execute the withdraw will have to
// send this amount as it is charged 1% fee
let new_amount = Perbill::from_rational(990u32, 1000u32) * unique_amount;

// 1% fee will be split between the executor and the treasury
let fee = Perbill::from_rational(10u32, 1000u32) * amount;

if let Some((asset_price, _)) = T::OracleInterface::get_oracle_key_value(asset_id) {
let asset_price_u128 = asset_price.saturated_into::<u128>();
let new_amount_u128 = new_amount.saturated_into::<u128>();

let new_amount_usdu_u128 = asset_price_u128
.checked_mul(new_amount_u128)
.ok_or(ArithmeticError::Overflow)?;

let asset_decimals = <T as pallet::Config>::Fungibles::decimals(asset_id);

let new_amount_usd_u128 = new_amount_usdu_u128
.checked_div(
(10u128)
.checked_pow(asset_decimals.into())
.ok_or(ArithmeticError::Overflow)?
.saturated_into::<u128>(),
)
.ok_or(ArithmeticError::Underflow)?;

let new_amount_usd_balance = new_amount_usd_u128.saturated_into::<BalanceOf<T>>();

let current_block_number = <frame_system::Pallet<T>>::block_number();

// `amount` represents the 99% of the original amount
// `fee` represents the 1% of the original amount
let withdraw = Withdraw {
withdraw_id,
user_address: who.clone(),
amount: new_amount,
usd_amount: new_amount_usd_balance,
asset_id,
withdraw_to_account: to.clone(),
statcode: StatCode::Pending,
timestamp: T::TimeProvider::now().as_secs(),
created_at: current_block_number.saturated_into::<u64>(),
fee,
transaction_hash: vec![],
can_vote_until: None,
};

PendingTransactions::<T>::insert(withdraw_id, true);

// Lock for withdraw
T::Fungibles::burn_from(asset_id, &who, amount, Exact, Polite)?;

WithdrawIdToWithdraw::<T>::insert(withdraw_id, withdraw);

WithdrawIdCounter::<T>::set(withdraw_id.saturating_add(1));

Self::deposit_event(Event::CreatedWithdraw {
who,
withdraw_id,
asset_id,
amount,
to,
});

Ok(())
} else {
Err(Error::<T>::NoOracleProvided.into())
}
}

/// The origin can cancel withdraw.
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
///
/// Emits `CancelWithdraw` event when successful.
#[pallet::call_index(1)]
pub fn cancel_withdraw(origin: OriginFor<T>, withdraw_id: WithdrawId) -> DispatchResult {
let mut who = ensure_signed(origin)?;

// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;

WithdrawIdToWithdraw::<T>::try_mutate(
withdraw_id,
|maybe_withdraw| -> DispatchResult {
let withdraw = maybe_withdraw.as_mut().ok_or(Error::<T>::NoWithdrawId)?;

ensure!(withdraw.user_address == who, Error::<T>::WithdrawIdNotMatch);
ensure!(withdraw.statcode == StatCode::Pending, Error::<T>::WithdrawNotPending);

PendingTransactions::<T>::mutate(withdraw_id, |withdraw| {
*withdraw = None;
});

withdraw.statcode = StatCode::Cancelled;

// Recreate the actual amount that was burned during withdraw initiation
// `withdraw.amount` represents 99% of the original amount
// `withdraw.fee` represents 1% of the original amount
let original_amount =
withdraw.amount.checked_add(&withdraw.fee).ok_or(Error::<T>::Overflow)?;

// mint asset back to withdrawer
T::Fungibles::mint_into(withdraw.asset_id, &who, original_amount)?;

Self::deposit_event(Event::CancelledWithdraw {
who,
withdraw_id: withdraw.withdraw_id,
});

Ok(())
},
)?;

Ok(())
}

/// The origin can execute a withdraw with ID `withdraw_id`, using the address
/// `from_address`.
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
/// - `from_address`: Native blockchain address.
///
/// Emits `ExecutingWithdraw` event when successful.
#[pallet::call_index(2)]
pub fn execute_withdraw(origin: OriginFor<T>, withdraw_id: WithdrawId) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
WithdrawIdToWithdraw::<T>::try_mutate(
withdraw_id,
|maybe_withdraw| -> DispatchResult {
let withdraw = maybe_withdraw.as_mut().ok_or(Error::<T>::NoWithdrawId)?;

ensure!(withdraw.statcode == StatCode::Pending, Error::<T>::WithdrawNotPending);

withdraw.statcode = StatCode::Executing;

WithdrawExecutionRequests::<T>::mutate(
withdraw_id,
|withdraw_execution| -> DispatchResult {
if withdraw_execution.is_none() {
*withdraw_execution = Some(WithdrawExecution {
user_address: who.clone(),
timestamp: T::TimeProvider::now().as_secs(),
});

Ok(())
} else {
Err(Error::<T>::WithdrawAlreadyBeingExecuted.into())
}
},
)?;

ExecutingTransactions::<T>::insert(withdraw_id, true);

Self::deposit_event(Event::<T>::ExecutingWithdraw { who, withdraw_id });

Ok(())
},
)?;

Ok(())
}

/// The origin can request a withdraw of a token to the native blockchain.
/// Supports chains that don't implement signature verification mechanism.
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
/// - `amount`: Amount of token.
/// - `to`: Native blockchain address.
///
/// Emits `CreatedWithdraw` event when successful.
#[pallet::call_index(3)]
#[pallet::weight({ 0 })]
pub fn withdraw_unprotected(
origin: OriginFor<T>,
asset_id: AssetId<T>,
amount: BalanceOf<T>,
to: BoundedVec<u8, T::StringLimit>,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;

ensure!(
<<T as pallet::Config>::Fungibles as frame_support::traits::fungibles::Inspect<
T::AccountId,
>>::asset_exists(asset_id),
Error::<T>::NoAssetId
);

ensure!(
asset_id != T::BitcoinAssetId::get() &&
asset_id != T::EthereumAssetId::get() &&
asset_id != T::NearAssetId::get() &&
asset_id != T::PolygonAssetId::get(),
Error::<T>::ProtectedAsset
);

ensure!(
T::Fungibles::balance(asset_id, &who) >= amount,
Error::<T>::NotEnoughAssetBalance
);

let withdraw_id = WithdrawIdCounter::<T>::get();

// Adding withdraw id to user withdraws vector
UserWithdrawsId::<T>::mutate(who.clone(), |val| -> DispatchResult {
val.push(withdraw_id);
Ok(())
})?;

// The one that accept to execute the withdraw will have to
// send this amount as it is charged 1% fee
let new_amount = Perbill::from_rational(990u32, 1000u32) * amount;

// 1% fee will be split between the executor and the treasury
let fee = Perbill::from_rational(10u32, 1000u32) * amount;

let current_block_number = <frame_system::Pallet<T>>::block_number();

// `amount` represents the 99% of the original amount
// `fee` represents the 1% of the original amount
let withdraw = Withdraw {
withdraw_id,
user_address: who.clone(),
amount: new_amount,
usd_amount: Default::default(),
asset_id,
withdraw_to_account: to.clone(),
statcode: StatCode::Pending,
timestamp: T::TimeProvider::now().as_secs(),
created_at: current_block_number.saturated_into::<u64>(),
fee,
transaction_hash: vec![],
can_vote_until: None,
};

PendingTransactions::<T>::insert(withdraw_id, true);

// Lock for withdraw
T::Fungibles::burn_from(asset_id, &who, amount, Exact, Polite)?;

WithdrawIdToWithdraw::<T>::insert(withdraw_id, withdraw);

WithdrawIdCounter::<T>::set(withdraw_id.saturating_add(1));

Self::deposit_event(Event::CreatedWithdraw { who, withdraw_id, asset_id, amount, to });

Ok(())
}

/// The origin can cancel the execution of a withdraw with ID `withdraw_id`
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
///
/// Emits `CancellingExecuteWithdraw` event when successful.
#[pallet::call_index(4)]
#[pallet::weight({ 0 })]
pub fn cancel_execute_withdraw(
origin: OriginFor<T>,
withdraw_id: WithdrawId,
) -> DispatchResult {
let mut who = ensure_signed(origin)?;
// Mutate the origin to transfer funds from the main account
who = T::SubAccounts::get_main_account(who)?;
WithdrawIdToWithdraw::<T>::try_mutate(
withdraw_id,
|maybe_withdraw| -> DispatchResult {
let withdraw = maybe_withdraw.as_mut().ok_or(Error::<T>::NoWithdrawId)?;

ensure!(
withdraw.statcode == StatCode::Executing,
Error::<T>::WithdrawNotExecuting
);

WithdrawExecutionRequests::<T>::mutate(
withdraw_id,
|withdraw_execution| -> DispatchResult {
if let Some(withdraw_execution_request) = withdraw_execution {
ensure!(
withdraw_execution_request.user_address == who,
Error::<T>::NoPermission
);

*withdraw_execution = None;

Ok(())
} else {
Err(Error::<T>::WithdrawNotBeingExecuted.into())
}
},
)?;

withdraw.statcode = StatCode::Pending;

ExecutingTransactions::<T>::remove(withdraw_id);

Self::deposit_event(Event::<T>::CancellingExecuteWithdraw { who, withdraw_id });

Ok(())
},
)?;

Ok(())
}
}

impl<T: Config>
WorkersAcceptWithdraw<
AssetId<T>,
T::AccountId,
BalanceOf<T>,
WithdrawId,
T::AddressLengthLimit,
Withdraw<AccountIdOf<T>, AssetId<T>, BalanceOf<T>, WithdrawId, T::StringLimit>,
> for Pallet<T>
{
/// The origin can accept a withdraw with ID `withdraw_id`, using the address `from_address`
/// and the transaction with hash `tx_hash`.
///
/// Parameters:
/// - `withdraw_id`: Withdraw id.
/// - `from_address`: Native blockchain address.
/// - `tx_hash`: Transaction hash in native blockchain.
/// Emits `WithdrawAccepted` event when successful.
fn accept_withdraw(
withdraw_id: WithdrawId,
tx_hash: BoundedVec<u8, T::AddressLengthLimit>,
) -> DispatchResult {
WithdrawIdToWithdraw::<T>::try_mutate(
withdraw_id,
|maybe_withdraw| -> DispatchResult {
let withdraw = maybe_withdraw.as_mut().ok_or(Error::<T>::NoWithdrawId)?;

ensure!(
withdraw.statcode == StatCode::Executing,
Error::<T>::WithdrawNotExecuting
);

// why are we hashing a hash?
let transaction_hash = T::Hashing::hash(&tx_hash);

let current_block = <frame_system::Pallet<T>>::block_number();

ProcessedTransactionsHash::<T>::try_mutate(
transaction_hash,
|maybe_transaction| -> DispatchResult {
if maybe_transaction.is_some() {
Err(Error::<T>::TransactionAlreadyProcessed.into())
} else {
*maybe_transaction = Some(true);
Ok(())
}
},
)?;

let withdraw_executor = WithdrawExecutionRequests::<T>::get(withdraw_id)
.ok_or(Error::<T>::WithdrawNotExecuting)?;

// `withdraw.amount` represents the 99% of the original amount
// `withdraw.fee` represents the 1% of the original amount

// Mint 99.5% of the original amount to the withdraw executor
// `withdraw.amount` (99% of original amount) is the amount that was
// transfered from the withdraw executor to the withdraw initiator's external
// account `withdraw.fee/2` (0.5% of the original amount) is the reward for
// executing a withdraw

let split_fee = withdraw
.fee
.checked_div(&<T as pallet::Config>::AssetBalance::from(2u32))
.ok_or(Error::<T>::Underflow)?;

let executor_amount =
withdraw.amount.checked_add(&split_fee).ok_or(Error::<T>::Overflow)?;

T::Fungibles::mint_into(
withdraw.asset_id,
&withdraw_executor.user_address,
executor_amount,
)?;

ensure!(PoolWorkerId::<T>::get().is_some(), Error::<T>::NoPoolId);

if let Some(pool_id) = PoolWorkerId::<T>::get() {
T::Fungibles::mint_into(withdraw.asset_id, &pool_id, split_fee)?;
}

if let Some(pool_id) = PoolWorkerId::<T>::get() {
T::WorkersRewards::sell_token(pool_id, withdraw.asset_id, split_fee)?;
}

if let Some(pool_id) = PoolWorkerId::<T>::get() {
T::WorkersRewards::buy_token(
pool_id.clone(),
T::UnitAssetId::get(),
T::Fungibles::balance(T::UsduId::get(), &pool_id),
)?;
}

withdraw.statcode = StatCode::Completed;
withdraw.can_vote_until = Some(
current_block
.saturating_add(T::VotingPeriodAfterConsensus::get())
.saturated_into::<u64>(),
);

ExecutingTransactions::<T>::remove(withdraw_id);

Ok(())
},
)?;

Self::deposit_event(Event::<T>::WithdrawAccepted { withdraw_id });

Ok(())
}

fn decline_withdraw(withdraw_id: WithdrawId) -> DispatchResult {
WithdrawIdToWithdraw::<T>::try_mutate(
withdraw_id,
|maybe_withdraw| -> DispatchResult {
let withdraw = maybe_withdraw.as_mut().ok_or(Error::<T>::NoWithdrawId)?;

let current_block = <frame_system::Pallet<T>>::block_number();

ensure!(
withdraw.statcode != StatCode::Cancelled,
Error::<T>::WithdrawAlreadyCancelled
);

withdraw.statcode = StatCode::Cancelled;
withdraw.can_vote_until = Some(
current_block
.saturating_add(T::VotingPeriodAfterConsensus::get())
.saturated_into::<u64>(),
);

// Recreate the actual amount that was burned during withdraw initiation
// `withdraw.amount` represents 99% of the original amount
// `withdraw.fee` represents 1% of the original amount
let original_amount =
withdraw.amount.checked_add(&withdraw.fee).ok_or(Error::<T>::Overflow)?;

// mint asset back to withdrawer
T::Fungibles::mint_into(
withdraw.asset_id,
&withdraw.user_address,
original_amount,
)?;

ExecutingTransactions::<T>::remove(withdraw_id);

Ok(())
},
)?;

Self::deposit_event(Event::<T>::WithdrawDeclined { withdraw_id });

Ok(())
}

fn get_pool_id_worker(pool_id: T::AccountId) -> DispatchResult {
PoolWorkerId::<T>::put(pool_id);

Ok(())
}

/// Getter for the withdraw info
fn get_withdraw_info(
withdraw_id: WithdrawId,
) -> Result<
Withdraw<AccountIdOf<T>, AssetId<T>, BalanceOf<T>, WithdrawId, T::StringLimit>,
DispatchError,
> {
let withdraw =
WithdrawIdToWithdraw::<T>::get(withdraw_id).ok_or(Error::<T>::NoWithdrawId)?;
Ok(withdraw)
}
}

impl<T: Config> Pallet<T> {
pub fn get_user_withdraw_history(
user_address: AccountIdOf<T>,
) -> Vec<WithdrawCreationOf<T>> {
let mut user_withdraws: Vec<WithdrawCreationOf<T>> = Vec::new();
for withdraw in WithdrawIdToWithdraw::<T>::iter_values() {
if withdraw.user_address == user_address {
user_withdraws.push(withdraw);
}
}
user_withdraws
}

pub fn u128_to_asset_balance(input: u128) -> BalanceOf<T> {
input.into()
}
}
}