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 DebugNoBound,
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, Debug, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo, MaxEncodedLen,
107)]
108#[scale_info(skip_type_params(C))]
109#[codec(mel_bound())]
110pub struct BalanceSwapAction<AccountId, C: ReservableCurrency<AccountId>> {
111 value: <C as Currency<AccountId>>::Balance,
112 _marker: PhantomData<C>,
113}
114
115impl<AccountId, C> BalanceSwapAction<AccountId, C>
116where
117 C: ReservableCurrency<AccountId>,
118{
119 pub fn new(value: <C as Currency<AccountId>>::Balance) -> Self {
121 Self { value, _marker: PhantomData }
122 }
123}
124
125impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C>
126where
127 C: ReservableCurrency<AccountId>,
128{
129 type Target = <C as Currency<AccountId>>::Balance;
130
131 fn deref(&self) -> &Self::Target {
132 &self.value
133 }
134}
135
136impl<AccountId, C> DerefMut for BalanceSwapAction<AccountId, C>
137where
138 C: ReservableCurrency<AccountId>,
139{
140 fn deref_mut(&mut self) -> &mut Self::Target {
141 &mut self.value
142 }
143}
144
145impl<T: Config, AccountId, C> SwapAction<AccountId, T> for BalanceSwapAction<AccountId, C>
146where
147 C: ReservableCurrency<AccountId>,
148{
149 fn reserve(&self, source: &AccountId) -> DispatchResult {
150 C::reserve(source, self.value)
151 }
152
153 fn claim(&self, source: &AccountId, target: &AccountId) -> bool {
154 C::repatriate_reserved(source, target, self.value, BalanceStatus::Free).is_ok()
155 }
156
157 fn weight(&self) -> Weight {
158 T::DbWeight::get().reads_writes(1, 1)
159 }
160
161 fn cancel(&self, source: &AccountId) {
162 C::unreserve(source, self.value);
163 }
164}
165
166pub use pallet::*;
167
168#[frame::pallet]
169pub mod pallet {
170 use super::*;
171
172 #[pallet::config]
174 pub trait Config: frame_system::Config {
175 #[allow(deprecated)]
177 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
178 type SwapAction: SwapAction<Self::AccountId, Self> + Parameter + MaxEncodedLen;
180 #[pallet::constant]
191 type ProofLimit: Get<u32>;
192 }
193
194 #[pallet::pallet]
195 pub struct Pallet<T>(_);
196
197 #[pallet::storage]
198 pub type PendingSwaps<T: Config> = StorageDoubleMap<
199 _,
200 Twox64Concat,
201 T::AccountId,
202 Blake2_128Concat,
203 HashedProof,
204 PendingSwap<T>,
205 >;
206
207 #[pallet::error]
208 pub enum Error<T> {
209 AlreadyExist,
211 InvalidProof,
213 ProofTooLarge,
215 SourceMismatch,
217 AlreadyClaimed,
219 NotExist,
221 ClaimActionMismatch,
223 DurationNotPassed,
225 }
226
227 #[pallet::event]
229 #[pallet::generate_deposit(pub(super) fn deposit_event)]
230 pub enum Event<T: Config> {
231 NewSwap { account: T::AccountId, proof: HashedProof, swap: PendingSwap<T> },
233 SwapClaimed { account: T::AccountId, proof: HashedProof, success: bool },
235 SwapCancelled { account: T::AccountId, proof: HashedProof },
237 }
238
239 #[pallet::call]
240 impl<T: Config> Pallet<T> {
241 #[pallet::call_index(0)]
254 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
255 pub fn create_swap(
256 origin: OriginFor<T>,
257 target: T::AccountId,
258 hashed_proof: HashedProof,
259 action: T::SwapAction,
260 duration: BlockNumberFor<T>,
261 ) -> DispatchResult {
262 let source = ensure_signed(origin)?;
263 ensure!(
264 !PendingSwaps::<T>::contains_key(&target, hashed_proof),
265 Error::<T>::AlreadyExist
266 );
267
268 action.reserve(&source)?;
269
270 let swap = PendingSwap {
271 source,
272 action,
273 end_block: frame_system::Pallet::<T>::block_number() + duration,
274 };
275 PendingSwaps::<T>::insert(target.clone(), hashed_proof, swap.clone());
276
277 Self::deposit_event(Event::NewSwap { account: target, proof: hashed_proof, swap });
278
279 Ok(())
280 }
281
282 #[pallet::call_index(1)]
290 #[pallet::weight(
291 T::DbWeight::get().reads_writes(1, 1)
292 .saturating_add(action.weight())
293 .ref_time()
294 .saturating_add(40_000_000)
295 .saturating_add((proof.len() as u64).saturating_mul(100))
296 )]
297 pub fn claim_swap(
298 origin: OriginFor<T>,
299 proof: Vec<u8>,
300 action: T::SwapAction,
301 ) -> DispatchResult {
302 ensure!(proof.len() <= T::ProofLimit::get() as usize, Error::<T>::ProofTooLarge);
303
304 let target = ensure_signed(origin)?;
305 let hashed_proof = blake2_256(&proof);
306
307 let swap =
308 PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::InvalidProof)?;
309 ensure!(swap.action == action, Error::<T>::ClaimActionMismatch);
310
311 let succeeded = swap.action.claim(&swap.source, &target);
312
313 PendingSwaps::<T>::remove(target.clone(), hashed_proof);
314
315 Self::deposit_event(Event::SwapClaimed {
316 account: target,
317 proof: hashed_proof,
318 success: succeeded,
319 });
320
321 Ok(())
322 }
323
324 #[pallet::call_index(2)]
331 #[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
332 pub fn cancel_swap(
333 origin: OriginFor<T>,
334 target: T::AccountId,
335 hashed_proof: HashedProof,
336 ) -> DispatchResult {
337 let source = ensure_signed(origin)?;
338
339 let swap = PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::NotExist)?;
340 ensure!(swap.source == source, Error::<T>::SourceMismatch);
341 ensure!(
342 frame_system::Pallet::<T>::block_number() >= swap.end_block,
343 Error::<T>::DurationNotPassed,
344 );
345
346 swap.action.cancel(&swap.source);
347 PendingSwaps::<T>::remove(&target, hashed_proof);
348
349 Self::deposit_event(Event::SwapCancelled { account: target, proof: hashed_proof });
350
351 Ok(())
352 }
353 }
354}