Skip to content
Snippets Groups Projects

Oneshot accounts

Merged Pascal Engélibert requested to merge poc-oneshot-accounts into master
Compare and
3 files
+ 304
3
Compare changes
  • Side-by-side
  • Inline
Files
3
@@ -26,7 +26,7 @@ use frame_support::traits::{OnUnbalanced, StoredMap};
@@ -26,7 +26,7 @@ use frame_support::traits::{OnUnbalanced, StoredMap};
use frame_system::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use pallet_provide_randomness::RequestId;
use pallet_provide_randomness::RequestId;
use sp_core::H256;
use sp_core::H256;
use sp_runtime::traits::{Convert, Saturating, Zero};
use sp_runtime::traits::{Convert, Saturating, StaticLookup, Zero};
#[frame_support::pallet]
#[frame_support::pallet]
pub mod pallet {
pub mod pallet {
@@ -60,6 +60,11 @@ pub mod pallet {
@@ -60,6 +60,11 @@ pub mod pallet {
// STORAGE //
// STORAGE //
 
#[pallet::storage]
 
#[pallet::getter(fn oneshot_account)]
 
pub type OneshotAccounts<T: Config> =
 
StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance, OptionQuery>;
 
#[pallet::storage]
#[pallet::storage]
#[pallet::getter(fn pending_random_id_assignments)]
#[pallet::getter(fn pending_random_id_assignments)]
pub type PendingRandomIdAssignments<T: Config> =
pub type PendingRandomIdAssignments<T: Config> =
@@ -133,14 +138,44 @@ pub mod pallet {
@@ -133,14 +138,44 @@ pub mod pallet {
/// the account creation price.
/// the account creation price.
/// [who, balance]
/// [who, balance]
ForceDestroy {
ForceDestroy {
 
balance: T::Balance,
who: T::AccountId,
who: T::AccountId,
 
},
 
OneshotAccountCreated {
 
account: T::AccountId,
balance: T::Balance,
balance: T::Balance,
 
creator: T::AccountId,
 
},
 
OneshotAccountConsumed {
 
account: T::AccountId,
 
dest1: (T::AccountId, T::Balance),
 
dest2: Option<(T::AccountId, T::Balance)>,
},
},
/// Random id assigned
/// Random id assigned
/// [account_id, random_id]
/// [account_id, random_id]
RandomIdAssigned { who: T::AccountId, random_id: H256 },
RandomIdAssigned { who: T::AccountId, random_id: H256 },
}
}
 
// ERRORS //
 
 
#[pallet::error]
 
pub enum Error<T> {
 
/// Block height is in the future
 
BlockHeightInFuture,
 
/// Block height is too old
 
BlockHeightTooOld,
 
/// Destination account does not exist
 
DestAccountNotExist,
 
/// Destination account has balance less than existential deposit
 
ExistentialDeposit,
 
/// Source account has insufficient balance
 
InsufficientBalance,
 
/// Destination oneshot account already exists
 
OneshotAccountAlreadyCreated,
 
/// Source oneshot account does not exist
 
OneshotAccountNotExist,
 
}
 
// HOOKS //
// HOOKS //
#[pallet::hooks]
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
@@ -219,6 +254,188 @@ pub mod pallet {
@@ -219,6 +254,188 @@ pub mod pallet {
total_weight
total_weight
}
}
}
}
 
 
// CALLS //
 
#[pallet::call]
 
impl<T: Config> Pallet<T> {
 
/// Create an account that can only be consumed once
 
///
 
/// - `dest`: The oneshot account to be created.
 
/// - `balance`: The balance to be transfered to this oneshot account.
 
#[pallet::weight(500_000_000)]
 
pub fn create_oneshot_account(
 
origin: OriginFor<T>,
 
dest: <T::Lookup as StaticLookup>::Source,
 
#[pallet::compact] value: T::Balance,
 
) -> DispatchResult {
 
let transactor = ensure_signed(origin)?;
 
let dest = T::Lookup::lookup(dest)?;
 
 
ensure!(
 
value >= T::ExistentialDeposit::get(),
 
Error::<T>::ExistentialDeposit
 
);
 
ensure!(
 
OneshotAccounts::<T>::get(&dest).is_none(),
 
Error::<T>::OneshotAccountAlreadyCreated
 
);
 
 
let transactor_data = frame_system::Account::<T>::get(&transactor).data;
 
let transactor_free_and_reserved = transactor_data
 
.free
 
.saturating_add(transactor_data.reserved);
 
let sufficient_balance = value.saturating_add(T::ExistentialDeposit::get());
 
ensure!(
 
transactor_data.free >= value && transactor_free_and_reserved >= sufficient_balance,
 
Error::<T>::InsufficientBalance
 
);
 
 
frame_system::Account::<T>::mutate(&transactor, |a| a.data.sub_free(value));
 
OneshotAccounts::<T>::insert(&dest, value);
 
Self::deposit_event(Event::OneshotAccountCreated {
 
account: dest,
 
balance: value,
 
creator: transactor,
 
});
 
 
Ok(())
 
}
 
/// Consume a oneshot account and transfer its balance to an account
 
///
 
/// - `block_height`: must be a recent block number. The limit is `BlockHashCount` in the past. (this is to prevent replay attacks)
 
/// - `dest`: `dest.1` is the destination account. If `dest.0` is `true`, then a oneshot account is created at `dest.1`. Else, `dest.1` has to be an existing account.
 
#[pallet::weight(500_000_000)]
 
pub fn consume_oneshot_account(
 
origin: OriginFor<T>,
 
block_height: T::BlockNumber,
 
dest: (bool, <T::Lookup as StaticLookup>::Source),
 
) -> DispatchResult {
 
let transactor = ensure_signed(origin)?;
 
let dest_is_oneshot = dest.0;
 
let dest = T::Lookup::lookup(dest.1)?;
 
 
let value = OneshotAccounts::<T>::take(&transactor)
 
.ok_or(Error::<T>::OneshotAccountNotExist)?;
 
 
ensure!(
 
block_height <= frame_system::Pallet::<T>::block_number(),
 
Error::<T>::BlockHeightInFuture
 
);
 
ensure!(
 
frame_system::pallet::BlockHash::<T>::contains_key(block_height),
 
Error::<T>::BlockHeightTooOld
 
);
 
if dest_is_oneshot {
 
ensure!(
 
OneshotAccounts::<T>::get(&dest).is_none(),
 
Error::<T>::OneshotAccountAlreadyCreated
 
);
 
OneshotAccounts::<T>::insert(&dest, value);
 
Self::deposit_event(Event::OneshotAccountCreated {
 
account: dest.clone(),
 
balance: value,
 
creator: transactor.clone(),
 
});
 
} else {
 
let dest_data = frame_system::Account::<T>::get(&dest).data;
 
ensure!(dest_data.was_providing(), Error::<T>::DestAccountNotExist);
 
frame_system::Account::<T>::mutate(&dest, |a| a.data.add_free(value));
 
}
 
OneshotAccounts::<T>::remove(&transactor);
 
Self::deposit_event(Event::OneshotAccountConsumed {
 
account: transactor,
 
dest1: (dest, value),
 
dest2: None,
 
});
 
 
Ok(())
 
}
 
/// Consume a oneshot account and transfer its balance to two accounts
 
///
 
/// - `block_height`: must be a recent block number. The limit is `BlockHashCount` in the past. (this is to prevent replay attacks)
 
/// - `dest1`: `dest1.1` is the destination account. If `dest1.0` is `true`, then a oneshot account is created at `dest1.1`. Else, `dest1.1` has to be an existing account.
 
/// - `dest2`: Idem.
 
/// - `balance1`: The amount transfered to `dest1`, the leftover being transfered to `dest2`.
 
#[pallet::weight(500_000_000)]
 
pub fn consume_oneshot_account_two_dests(
 
origin: OriginFor<T>,
 
block_height: T::BlockNumber,
 
dest1: (bool, <T::Lookup as StaticLookup>::Source),
 
dest2: (bool, <T::Lookup as StaticLookup>::Source),
 
#[pallet::compact] balance1: T::Balance,
 
) -> DispatchResult {
 
let transactor = ensure_signed(origin)?;
 
let dest1_is_oneshot = dest1.0;
 
let dest1 = T::Lookup::lookup(dest1.1)?;
 
let dest2_is_oneshot = dest2.0;
 
let dest2 = T::Lookup::lookup(dest2.1)?;
 
 
let value = OneshotAccounts::<T>::take(&transactor)
 
.ok_or(Error::<T>::OneshotAccountNotExist)?;
 
 
ensure!(value > balance1, Error::<T>::InsufficientBalance);
 
let balance2 = value.saturating_sub(balance1);
 
ensure!(
 
block_height <= frame_system::Pallet::<T>::block_number(),
 
Error::<T>::BlockHeightInFuture
 
);
 
ensure!(
 
frame_system::pallet::BlockHash::<T>::contains_key(block_height),
 
Error::<T>::BlockHeightTooOld
 
);
 
if dest1_is_oneshot {
 
ensure!(
 
OneshotAccounts::<T>::get(&dest1).is_none(),
 
Error::<T>::OneshotAccountAlreadyCreated
 
);
 
ensure!(
 
balance1 >= T::ExistentialDeposit::get(),
 
Error::<T>::ExistentialDeposit
 
);
 
} else {
 
let dest1_data = frame_system::Account::<T>::get(&dest1).data;
 
ensure!(dest1_data.was_providing(), Error::<T>::DestAccountNotExist);
 
}
 
if dest2_is_oneshot {
 
ensure!(
 
OneshotAccounts::<T>::get(&dest2).is_none(),
 
Error::<T>::OneshotAccountAlreadyCreated
 
);
 
ensure!(
 
balance2 >= T::ExistentialDeposit::get(),
 
Error::<T>::ExistentialDeposit
 
);
 
OneshotAccounts::<T>::insert(&dest2, balance2);
 
Self::deposit_event(Event::OneshotAccountCreated {
 
account: dest2.clone(),
 
balance: balance2,
 
creator: transactor.clone(),
 
});
 
} else {
 
let dest2_data = frame_system::Account::<T>::get(&dest2).data;
 
ensure!(dest2_data.was_providing(), Error::<T>::DestAccountNotExist);
 
frame_system::Account::<T>::mutate(&dest2, |a| a.data.add_free(balance2));
 
}
 
if dest1_is_oneshot {
 
OneshotAccounts::<T>::insert(&dest1, balance1);
 
Self::deposit_event(Event::OneshotAccountCreated {
 
account: dest1.clone(),
 
balance: balance1,
 
creator: transactor.clone(),
 
});
 
} else {
 
frame_system::Account::<T>::mutate(&dest1, |a| a.data.add_free(balance1));
 
}
 
OneshotAccounts::<T>::remove(&transactor);
 
Self::deposit_event(Event::OneshotAccountConsumed {
 
account: transactor,
 
dest1: (dest1, balance1),
 
dest2: Some((dest2, balance2)),
 
});
 
 
Ok(())
 
}
 
}
}
}
impl<T> pallet_provide_randomness::OnFilledRandomness for Pallet<T>
impl<T> pallet_provide_randomness::OnFilledRandomness for Pallet<T>
Loading