#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
pub mod mock;
#[cfg(test)]
pub mod tests;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod weights;
use codec::{Decode, Encode};
use frame_support::{debug, dispatch, ensure, traits::Get, weights::Weight};
use frame_system::ensure_signed;
use merkle::{
utils::{
keys::{Commitment, ScalarData},
permissions::ensure_admin,
},
Group as GroupTrait, Module as MerkleModule,
};
use orml_traits::MultiCurrency;
use sp_runtime::{
traits::{AccountIdConversion, Zero},
ModuleId,
};
use sp_std::prelude::*;
use weights::WeightInfo;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::DispatchResultWithInfo;
#[pallet::config]
pub trait Config: frame_system::Config + merkle::Config + orml_tokens::Config + orml_currencies::Config {
#[pallet::constant]
type ModuleId: Get<ModuleId>;
type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
type Currency: MultiCurrency<Self::AccountId>;
#[pallet::constant]
type NativeCurrencyId: Get<CurrencyIdOf<Self>>;
type Group: GroupTrait<Self::AccountId, Self::BlockNumber, Self::GroupId>;
#[pallet::constant]
type DepositLength: Get<Self::BlockNumber>;
#[pallet::constant]
type DefaultAdmin: Get<Self::AccountId>;
type WeightInfo: WeightInfo;
type MixerSizes: Get<Vec<BalanceOf<Self>>>;
}
#[pallet::storage]
#[pallet::getter(fn initialised)]
pub type Initialised<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn mixer_groups)]
pub type MixerGroups<T: Config> = StorageMap<_, Blake2_128Concat, T::GroupId, MixerInfo<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn mixer_group_ids)]
pub type MixerGroupIds<T: Config> = StorageValue<_, Vec<T::GroupId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn admin)]
pub type Admin<T: Config> = StorageValue<_, T::AccountId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn total_value_locked)]
pub type TotalValueLocked<T: Config> = StorageMap<_, Blake2_128Concat, T::GroupId, BalanceOf<T>, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(<T as frame_system::Config>::AccountId = "AccountId", <T as merkle::Config>::GroupId = "GroupId")]
pub enum Event<T: Config> {
Deposit(
<T as merkle::Config>::GroupId,
<T as frame_system::Config>::AccountId,
ScalarData,
),
Withdraw(
<T as merkle::Config>::GroupId,
<T as frame_system::Config>::AccountId,
ScalarData,
),
}
#[pallet::error]
pub enum Error<T> {
NoneValue,
NoMixerForId,
NotInitialised,
AlreadyInitialised,
InsufficientBalance,
UnauthorizedCall,
MixerStopped,
}
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
if Self::initialised() {
<T as Config>::WeightInfo::on_finalize_initialized()
} else {
<T as Config>::WeightInfo::on_finalize_uninitialized()
}
}
fn on_finalize(_n: BlockNumberFor<T>) {
if Self::initialised() {
let mixer_ids = MixerGroupIds::<T>::get();
for i in 0..mixer_ids.len() {
let cached_roots = <merkle::Module<T>>::cached_roots(_n, mixer_ids[i]);
if cached_roots.len() == 0 {
let _ = <merkle::Module<T>>::add_root_to_cache(mixer_ids[i], _n);
}
}
} else {
match Self::initialize() {
Ok(_) => {}
Err(e) => {
debug::native::error!("Error initialising: {:?}", e);
}
}
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(<T as Config>::WeightInfo::deposit(data_points.len() as u32))]
pub fn deposit(
origin: OriginFor<T>,
mixer_id: T::GroupId,
data_points: Vec<ScalarData>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(Self::initialised(), Error::<T>::NotInitialised);
ensure!(!<MerkleModule<T>>::stopped(mixer_id), Error::<T>::MixerStopped);
let mut mixer_info = Self::get_mixer(mixer_id)?;
let balance = T::Currency::free_balance(mixer_info.currency_id, &sender);
let deposit: BalanceOf<T> = data_points
.iter()
.map(|_| mixer_info.fixed_deposit_size)
.fold(Zero::zero(), |acc, elt| acc + elt);
ensure!(balance >= deposit, Error::<T>::InsufficientBalance);
T::Currency::transfer(mixer_info.currency_id, &sender, &Self::account_id(), deposit)?;
let tvl = Self::total_value_locked(mixer_id);
<TotalValueLocked<T>>::insert(mixer_id, tvl + deposit);
T::Group::add_members(Self::account_id(), mixer_id.into(), data_points.clone())?;
mixer_info.leaves.extend(data_points);
MixerGroups::<T>::insert(mixer_id, mixer_info);
Ok(().into())
}
#[pallet::weight(<T as Config>::WeightInfo::withdraw())]
pub fn withdraw(origin: OriginFor<T>, withdraw_proof: WithdrawProof<T>) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(Self::initialised(), Error::<T>::NotInitialised);
ensure!(
!<MerkleModule<T>>::stopped(withdraw_proof.mixer_id),
Error::<T>::MixerStopped
);
let recipient = withdraw_proof.recipient.unwrap_or(sender.clone());
let relayer = withdraw_proof.relayer.unwrap_or(sender.clone());
let mixer_info = MixerGroups::<T>::get(withdraw_proof.mixer_id);
T::Group::has_used_nullifier(withdraw_proof.mixer_id.into(), withdraw_proof.nullifier_hash)?;
T::Group::verify_zk_membership_proof(
withdraw_proof.mixer_id.into(),
withdraw_proof.cached_block,
withdraw_proof.cached_root,
withdraw_proof.comms,
withdraw_proof.nullifier_hash,
withdraw_proof.proof_bytes,
withdraw_proof.leaf_index_commitments,
withdraw_proof.proof_commitments,
ScalarData::from_slice(&recipient.encode()),
ScalarData::from_slice(&relayer.encode()),
)?;
T::Currency::transfer(
mixer_info.currency_id,
&Self::account_id(),
&recipient,
mixer_info.fixed_deposit_size,
)?;
let tvl = Self::total_value_locked(withdraw_proof.mixer_id);
<TotalValueLocked<T>>::insert(withdraw_proof.mixer_id, tvl - mixer_info.fixed_deposit_size);
T::Group::add_nullifier(
Self::account_id(),
withdraw_proof.mixer_id.into(),
withdraw_proof.nullifier_hash,
)?;
Ok(().into())
}
#[pallet::weight(0)]
pub fn create_new(
origin: OriginFor<T>,
currency_id: CurrencyIdOf<T>,
size: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
ensure_admin(origin, &Self::admin())?;
let depth: u8 = <T as merkle::Config>::MaxTreeDepth::get();
let mixer_id: T::GroupId = T::Group::create_group(Self::account_id(), true, depth)?;
let mixer_info = MixerInfo::<T>::new(T::DepositLength::get(), size, Vec::new(), currency_id);
MixerGroups::<T>::insert(mixer_id, mixer_info);
Ok(().into())
}
#[pallet::weight(<T as Config>::WeightInfo::set_stopped())]
pub fn set_stopped(origin: OriginFor<T>, stopped: bool) -> DispatchResultWithPostInfo {
ensure_admin(origin, &Self::admin())?;
let mixer_ids = MixerGroupIds::<T>::get();
for i in 0..mixer_ids.len() {
T::Group::set_stopped(Self::account_id(), mixer_ids[i], stopped)?;
}
Ok(().into())
}
#[pallet::weight(<T as Config>::WeightInfo::transfer_admin())]
pub fn transfer_admin(origin: OriginFor<T>, to: T::AccountId) -> DispatchResultWithPostInfo {
ensure_admin(origin, &Self::admin())?;
Admin::<T>::set(to);
Ok(().into())
}
}
}
#[derive(Encode, Decode, PartialEq, Clone)]
pub struct WithdrawProof<T: Config> {
mixer_id: T::GroupId,
cached_block: T::BlockNumber,
cached_root: ScalarData,
comms: Vec<Commitment>,
nullifier_hash: ScalarData,
proof_bytes: Vec<u8>,
leaf_index_commitments: Vec<Commitment>,
proof_commitments: Vec<Commitment>,
recipient: Option<T::AccountId>,
relayer: Option<T::AccountId>,
}
impl<T: Config> WithdrawProof<T> {
pub fn new(
mixer_id: T::GroupId,
cached_block: T::BlockNumber,
cached_root: ScalarData,
comms: Vec<Commitment>,
nullifier_hash: ScalarData,
proof_bytes: Vec<u8>,
leaf_index_commitments: Vec<Commitment>,
proof_commitments: Vec<Commitment>,
recipient: Option<T::AccountId>,
relayer: Option<T::AccountId>,
) -> Self {
Self {
mixer_id,
cached_block,
cached_root,
comms,
nullifier_hash,
proof_bytes,
leaf_index_commitments,
proof_commitments,
recipient,
relayer,
}
}
}
#[cfg(feature = "std")]
impl<T: Config> std::fmt::Debug for WithdrawProof<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
pub type BalanceOf<T> = <<T as Config>::Currency as MultiCurrency<<T as frame_system::Config>::AccountId>>::Balance;
pub type CurrencyIdOf<T> =
<<T as pallet::Config>::Currency as MultiCurrency<<T as frame_system::Config>::AccountId>>::CurrencyId;
#[derive(Encode, Decode, PartialEq)]
pub struct MixerInfo<T: Config> {
pub minimum_deposit_length_for_reward: T::BlockNumber,
pub fixed_deposit_size: BalanceOf<T>,
pub leaves: Vec<ScalarData>,
pub currency_id: CurrencyIdOf<T>,
}
impl<T: Config> core::default::Default for MixerInfo<T> {
fn default() -> Self {
Self {
minimum_deposit_length_for_reward: Zero::zero(),
fixed_deposit_size: Zero::zero(),
leaves: Vec::new(),
currency_id: T::NativeCurrencyId::get(),
}
}
}
impl<T: Config> MixerInfo<T> {
pub fn new(
min_dep_length: T::BlockNumber,
dep_size: BalanceOf<T>,
leaves: Vec<ScalarData>,
currency_id: CurrencyIdOf<T>,
) -> Self {
Self {
minimum_deposit_length_for_reward: min_dep_length,
fixed_deposit_size: dep_size,
leaves,
currency_id,
}
}
}
impl<T: Config> Module<T> {
pub fn account_id() -> T::AccountId {
T::ModuleId::get().into_account()
}
pub fn get_mixer(mixer_id: T::GroupId) -> Result<MixerInfo<T>, dispatch::DispatchError> {
let mixer_info = MixerGroups::<T>::get(mixer_id);
ensure!(mixer_info.fixed_deposit_size > Zero::zero(), Error::<T>::NoMixerForId);
Ok(mixer_info)
}
pub fn initialize() -> dispatch::DispatchResult {
ensure!(!Self::initialised(), Error::<T>::AlreadyInitialised);
let default_admin = T::DefaultAdmin::get();
Admin::<T>::set(default_admin);
let depth: u8 = <T as merkle::Config>::MaxTreeDepth::get();
let sizes = T::MixerSizes::get();
let mut mixer_ids = Vec::new();
for size in sizes.into_iter() {
let mixer_id: T::GroupId = T::Group::create_group(Self::account_id(), true, depth)?;
let mixer_info = MixerInfo::<T>::new(T::DepositLength::get(), size, Vec::new(), T::NativeCurrencyId::get());
MixerGroups::<T>::insert(mixer_id, mixer_info);
mixer_ids.push(mixer_id);
}
MixerGroupIds::<T>::set(mixer_ids);
Initialised::<T>::set(true);
Ok(())
}
}