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};
use frame_system::pallet_prelude::*;
use pallet_provide_randomness::RequestId;
use sp_core::H256;
use sp_runtime::traits::{Convert, Saturating, Zero};
use sp_runtime::traits::{Convert, Saturating, StaticLookup, Zero};
#[frame_support::pallet]
pub mod pallet {
@@ -60,6 +60,11 @@ pub mod pallet {
// STORAGE //
#[pallet::storage]
#[pallet::getter(fn oneshot_account)]
pub type OneshotAccounts<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn pending_random_id_assignments)]
pub type PendingRandomIdAssignments<T: Config> =
@@ -133,14 +138,44 @@ pub mod pallet {
/// the account creation price.
/// [who, balance]
ForceDestroy {
balance: T::Balance,
who: T::AccountId,
},
OneshotAccountCreated {
account: T::AccountId,
balance: T::Balance,
creator: T::AccountId,
},
OneshotAccountConsumed {
account: T::AccountId,
dest1: (T::AccountId, T::Balance),
dest2: Option<(T::AccountId, T::Balance)>,
},
/// Random id assigned
/// [account_id, random_id]
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 //
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
@@ -219,6 +254,188 @@ pub mod pallet {
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>
Loading