Skip to content
Snippets Groups Projects
Commit e775eae8 authored by Pascal Engélibert's avatar Pascal Engélibert :bicyclist:
Browse files

feat(duniter-account): finalized PoC oneshot account

parent aad8ae8b
No related branches found
No related tags found
No related merge requests found
......@@ -148,8 +148,8 @@ pub mod pallet {
},
OneshotAccountConsumed {
account: T::AccountId,
balance: T::Balance,
dest: T::AccountId,
dest1: (T::AccountId, T::Balance),
dest2: Option<(T::AccountId, T::Balance)>,
},
/// Random id assigned
/// [account_id, random_id]
......@@ -160,15 +160,19 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {
/// DestAccountNotExist
/// Block height is in the future
BlockHeightInFuture,
/// Block height is too old
BlockHeightTooOld,
/// Destination account does not exist
DestAccountNotExist,
/// ExistentialDeposit
/// Destination account has balance less than existential deposit
ExistentialDeposit,
/// InsufficientBalance
/// Source account has insufficient balance
InsufficientBalance,
/// OneshotAccouncAlreadyCreated
/// Destination oneshot account already exists
OneshotAccountAlreadyCreated,
/// OneshotAccountNotExist
/// Source oneshot account does not exist
OneshotAccountNotExist,
}
......@@ -254,6 +258,10 @@ pub mod pallet {
// 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>,
......@@ -292,9 +300,14 @@ pub mod pallet {
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)?;
......@@ -304,31 +317,121 @@ pub mod pallet {
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::OneshotAccountConsumed {
account: transactor.clone(),
balance: value,
dest: dest.clone(),
});
Self::deposit_event(Event::OneshotAccountCreated {
account: dest,
account: dest.clone(),
balance: value,
creator: transactor,
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,
balance: value,
dest,
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(())
}
......
......@@ -29,6 +29,9 @@ pub struct AccountData<Balance> {
}
impl<Balance: Copy + Saturating + Zero> AccountData<Balance> {
pub fn add_free(&mut self, amount: Balance) {
self.free = self.free.saturating_add(amount);
}
pub fn free_and_reserved(&self) -> Balance {
self.free.saturating_add(self.reserved)
}
......
......@@ -344,3 +344,78 @@ fn test_create_new_idty() {
);
});
}
#[test]
fn test_oneshot_accounts() {
ExtBuilder::new(1, 3, 4)
.with_initial_balances(vec![(AccountKeyring::Alice.to_account_id(), 1_000)])
.build()
.execute_with(|| {
run_to_block(6);
assert_ok!(Account::create_oneshot_account(
frame_system::RawOrigin::Signed(AccountKeyring::Alice.to_account_id()).into(),
MultiAddress::Id(AccountKeyring::Eve.to_account_id()),
400
));
assert_eq!(
Balances::free_balance(AccountKeyring::Alice.to_account_id()),
600
);
run_to_block(7);
assert_ok!(Account::consume_oneshot_account_two_dests(
frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
0,
(
true,
MultiAddress::Id(AccountKeyring::Ferdie.to_account_id())
),
(
false,
MultiAddress::Id(AccountKeyring::Alice.to_account_id())
),
300
));
assert_eq!(
Balances::free_balance(AccountKeyring::Alice.to_account_id()),
700
);
assert_err!(
Account::consume_oneshot_account(
frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
0,
(
true,
MultiAddress::Id(AccountKeyring::Ferdie.to_account_id())
),
),
pallet_duniter_account::Error::<Runtime>::OneshotAccountNotExist
);
run_to_block(8);
assert_ok!(Account::consume_oneshot_account(
frame_system::RawOrigin::Signed(AccountKeyring::Ferdie.to_account_id()).into(),
0,
(
false,
MultiAddress::Id(AccountKeyring::Alice.to_account_id())
),
));
assert_eq!(
Balances::free_balance(AccountKeyring::Alice.to_account_id()),
1000
);
assert_err!(
Account::consume_oneshot_account(
frame_system::RawOrigin::Signed(AccountKeyring::Eve.to_account_id()).into(),
0,
(
false,
MultiAddress::Id(AccountKeyring::Alice.to_account_id())
),
),
pallet_duniter_account::Error::<Runtime>::OneshotAccountNotExist
);
});
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment