1#![cfg_attr(not(feature = "std"), no_std)]
42
43mod tests;
44
45extern crate alloc;
46
47use alloc::vec::Vec;
48use codec::{Decode, DecodeWithMemTracking, Encode};
49use core::{
50 marker::PhantomData,
51 ops::{Deref, DerefMut},
52};
53use frame::{
54 prelude::*,
55 traits::{BalanceStatus, Currency, ReservableCurrency},
56};
57use scale_info::TypeInfo;
58
59#[derive(
61 Clone,
62 Eq,
63 PartialEq,
64 RuntimeDebugNoBound,
65 Encode,
66 Decode,
67 DecodeWithMemTracking,
68 TypeInfo,
69 MaxEncodedLen,
70)]
71#[scale_info(skip_type_params(T))]
72#[codec(mel_bound())]
73pub struct PendingSwap<T: Config> {
74 pub source: T::AccountId,
76 pub action: T::SwapAction,
78 pub end_block: BlockNumberFor<T>,
80}
81
82pub type HashedProof = [u8; 32];
84
85pub trait SwapAction<AccountId, T: Config> {
92 fn reserve(&self, source: &AccountId) -> DispatchResult;
95 fn claim(&self, source: &AccountId, target: &AccountId) -> bool;
98 fn weight(&self) -> Weight;
100 fn cancel(&self, source: &AccountId);
102}
103
104#[derive(
106 Clone,
107 RuntimeDebug,
108 Eq,
109 PartialEq,
110 Encode,
111 Decode,
112 DecodeWithMemTracking,
113 TypeInfo,
114 MaxEncodedLen,
115)]
116#[scale_info(skip_type_params(C))]
117#[codec(mel_bound())]
118pub struct BalanceSwapAction<AccountId, C: ReservableCurrency<AccountId>> {
119 value: <C as Currency<AccountId>>::Balance,
120 _marker: PhantomData<C>,
121}
122
123impl<AccountId, C> BalanceSwapAction<AccountId, C>
124where
125 C: ReservableCurrency<AccountId>,
126{
127 pub fn new(value: <C as Currency<AccountId>>::Balance) -> Self {
129 Self { value, _marker: PhantomData }
130 }
131}
132
133impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C>
134where
135 C: ReservableCurrency<AccountId>,
136{
137 type Target = <C as Currency<AccountId>>::Balance;
138
139 fn deref(&self) -> &Self::Target {
140 &self.value
141 }
142}
143
144impl<AccountId, C> DerefMut for BalanceSwapAction<AccountId, C>
145where
146 C: ReservableCurrency<AccountId>,
147{
148 fn deref_mut(&mut self) -> &mut Self::Target {
149 &mut self.value
150 }
151}
152
153impl<T: Config, AccountId, C> SwapAction<AccountId, T> for BalanceSwapAction<AccountId, C>
154where
155 C: ReservableCurrency<AccountId>,
156{
157 fn reserve(&self, source: &AccountId) -> DispatchResult {
158 C::reserve(source, self.value)
159 }
160
161 fn claim(&self, source: &AccountId, target: &AccountId) -> bool {
162 C::repatriate_reserved(source, target, self.value, BalanceStatus::Free).is_ok()
163 }
164
165 fn weight(&self) -> Weight {
166 T::DbWeight::get().reads_writes(1, 1)
167 }
168
169 fn cancel(&self, source: &AccountId) {
170 C::unreserve(source, self.value);
171 }
172}
173
174pub use pallet::*;
175
176#[frame::pallet]
177pub mod pallet {
178 use super::*;
179
180 #[pallet::config]
182 pub trait Config: frame_system::Config {
183 #[allow(deprecated)]
185 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
186 type SwapAction: SwapAction<Self::AccountId, Self> + Parameter + MaxEncodedLen;
188 #[pallet::constant]
199 type ProofLimit: Get<u32>;
200 }
201
202 #[pallet::pallet]
203 pub struct Pallet<T>(_);
204
205 #[pallet::storage]
206 pub type PendingSwaps<T: Config> = StorageDoubleMap<
207 _,
208 Twox64Concat,
209 T::AccountId,
210 Blake2_128Concat,
211 HashedProof,
212 PendingSwap<T>,
213 >;
214
215 #[pallet::error]
216 pub enum Error<T> {
217 AlreadyExist,
219 InvalidProof,
221 ProofTooLarge,
223 SourceMismatch,
225 AlreadyClaimed,
227 NotExist,
229 ClaimActionMismatch,
231 DurationNotPassed,
233 }
234
235 #[pallet::event]
237 #[pallet::generate_deposit(pub(super) fn deposit_event)]
238 pub enum Event<T: Config> {
239 NewSwap { account: T::AccountId, proof: HashedProof, swap: PendingSwap<T> },
241 SwapClaimed { account: T::AccountId, proof: HashedProof, success: bool },
243 SwapCancelled { account: T::AccountId, proof: HashedProof },
245 }
246
247 #[pallet::call]
248 impl<T: Config> Pallet<T> {
249 #[pallet::call_index(0)]
262 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
263 pub fn create_swap(
264 origin: OriginFor<T>,
265 target: T::AccountId,
266 hashed_proof: HashedProof,
267 action: T::SwapAction,
268 duration: BlockNumberFor<T>,
269 ) -> DispatchResult {
270 let source = ensure_signed(origin)?;
271 ensure!(
272 !PendingSwaps::<T>::contains_key(&target, hashed_proof),
273 Error::<T>::AlreadyExist
274 );
275
276 action.reserve(&source)?;
277
278 let swap = PendingSwap {
279 source,
280 action,
281 end_block: frame_system::Pallet::<T>::block_number() + duration,
282 };
283 PendingSwaps::<T>::insert(target.clone(), hashed_proof, swap.clone());
284
285 Self::deposit_event(Event::NewSwap { account: target, proof: hashed_proof, swap });
286
287 Ok(())
288 }
289
290 #[pallet::call_index(1)]
298 #[pallet::weight(
299 T::DbWeight::get().reads_writes(1, 1)
300 .saturating_add(action.weight())
301 .ref_time()
302 .saturating_add(40_000_000)
303 .saturating_add((proof.len() as u64).saturating_mul(100))
304 )]
305 pub fn claim_swap(
306 origin: OriginFor<T>,
307 proof: Vec<u8>,
308 action: T::SwapAction,
309 ) -> DispatchResult {
310 ensure!(proof.len() <= T::ProofLimit::get() as usize, Error::<T>::ProofTooLarge);
311
312 let target = ensure_signed(origin)?;
313 let hashed_proof = blake2_256(&proof);
314
315 let swap =
316 PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::InvalidProof)?;
317 ensure!(swap.action == action, Error::<T>::ClaimActionMismatch);
318
319 let succeeded = swap.action.claim(&swap.source, &target);
320
321 PendingSwaps::<T>::remove(target.clone(), hashed_proof);
322
323 Self::deposit_event(Event::SwapClaimed {
324 account: target,
325 proof: hashed_proof,
326 success: succeeded,
327 });
328
329 Ok(())
330 }
331
332 #[pallet::call_index(2)]
339 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
340 pub fn cancel_swap(
341 origin: OriginFor<T>,
342 target: T::AccountId,
343 hashed_proof: HashedProof,
344 ) -> DispatchResult {
345 let source = ensure_signed(origin)?;
346
347 let swap = PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::NotExist)?;
348 ensure!(swap.source == source, Error::<T>::SourceMismatch);
349 ensure!(
350 frame_system::Pallet::<T>::block_number() >= swap.end_block,
351 Error::<T>::DurationNotPassed,
352 );
353
354 swap.action.cancel(&swap.source);
355 PendingSwaps::<T>::remove(&target, hashed_proof);
356
357 Self::deposit_event(Event::SwapCancelled { account: target, proof: hashed_proof });
358
359 Ok(())
360 }
361 }
362}