Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add erc20 compatible allowances #950

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion tokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub struct ReserveData<ReserveIdentifier, Balance> {
pub amount: Balance,
}

/// balance information for an account.
/// Balance information for an account.
#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, MaxEncodedLen, RuntimeDebug, TypeInfo)]
pub struct AccountData<Balance> {
/// Non-reserved part of the balance. There may still be restrictions on
Expand Down Expand Up @@ -256,6 +256,8 @@ pub mod module {
DeadAccount,
// Number of named reserves exceed `T::MaxReserves`
TooManyReserves,
/// The allowance is too low
AllowanceTooLow,
}

#[pallet::event]
Expand Down Expand Up @@ -366,6 +368,13 @@ pub mod module {
currency_id: T::CurrencyId,
amount: T::Balance,
},
/// Some allowance was updated
AllowanceSet {
currency_id: T::CurrencyId,
owner: T::AccountId,
spender: T::AccountId,
amount: T::Balance,
},
}

/// The total issuance of a token type.
Expand Down Expand Up @@ -418,6 +427,19 @@ pub mod module {
ValueQuery,
>;

/// Allowances for accounts to spend approved funds.
#[pallet::storage]
pub(super) type Allowances<T: Config> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, T::CurrencyId>, // currency
NMapKey<Blake2_128Concat, T::AccountId>, // owner
NMapKey<Blake2_128Concat, T::AccountId>, // spender
),
T::Balance, // amount
ValueQuery,
>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub balances: Vec<(T::AccountId, T::CurrencyId, T::Balance)>,
Expand Down Expand Up @@ -649,6 +671,84 @@ pub mod module {

Ok(())
}

/// Approve the `spender` to spend an `amount` from the free balance of `owner`.
///
/// The dispatch origin for this call must be `Signed` by the owner.
///
/// - `spender`: The account to approve spending.
/// - `currency_id`: currency type.
/// - `amount`: free balance amount to allow.
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::set_allowance())]
pub fn set_allowance(
origin: OriginFor<T>,
spender: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let owner = ensure_signed(origin)?;
let spender = T::Lookup::lookup(spender)?;

Allowances::<T>::insert((&currency_id, &owner, &spender), amount);

Self::deposit_event(Event::AllowanceSet {
currency_id,
owner,
spender,
amount,
});

Ok(())
}

/// Spender can transfer some free balance from the approved account.
///
/// The dispatch origin for this call must be `Signed` by the spender.
///
/// - `from`: The account which approved spending.
/// - `from`: The recipient of the transfer.
/// - `currency_id`: currency type.
/// - `amount`: free balance amount to allow.
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::set_balance())]
pub fn transfer_allowance(
origin: OriginFor<T>,
from: <T::Lookup as StaticLookup>::Source,
to: <T::Lookup as StaticLookup>::Source,
currency_id: T::CurrencyId,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let spender = ensure_signed(origin)?;
let owner = T::Lookup::lookup(from)?;
let destination = T::Lookup::lookup(to)?;

let remaining = Allowances::<T>::try_mutate(
(&currency_id, &owner, &spender),
|allowance| -> Result<T::Balance, DispatchError> {
let remaining = allowance.checked_sub(&amount).ok_or(Error::<T>::AllowanceTooLow)?;
*allowance = remaining;
Ok(remaining)
},
)?;

Self::deposit_event(Event::AllowanceSet {
currency_id,
owner: owner.clone(),
spender,
amount: remaining,
});

Self::do_transfer(
currency_id,
&owner,
&destination,
amount,
ExistenceRequirement::KeepAlive,
)?;

Ok(())
}
}
}

Expand Down
53 changes: 53 additions & 0 deletions tokens/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,59 @@ fn set_balance_should_work() {
});
}

#[test]
fn set_allowance_should_work() {
ExtBuilder::default()
.balances(vec![(ALICE, DOT, 100)])
.build()
.execute_with(|| {
assert_eq!(Allowances::<Runtime>::get((DOT, ALICE, BOB)), 0);
assert_ok!(Tokens::set_allowance(Some(ALICE).into(), BOB, DOT, 100));
assert_eq!(Allowances::<Runtime>::get((DOT, ALICE, BOB)), 100);

System::assert_last_event(RuntimeEvent::Tokens(crate::Event::AllowanceSet {
currency_id: DOT,
owner: ALICE,
spender: BOB,
amount: 100,
}));
});
}

#[test]
fn transfer_allowance_should_work() {
ExtBuilder::default()
.balances(vec![(ALICE, DOT, 200)])
.build()
.execute_with(|| {
assert_eq!(Tokens::free_balance(DOT, &ALICE), 200);

assert_ok!(Tokens::set_allowance(Some(ALICE).into(), BOB, DOT, 200));
System::assert_has_event(RuntimeEvent::Tokens(crate::Event::AllowanceSet {
currency_id: DOT,
owner: ALICE,
spender: BOB,
amount: 200,
}));

assert_ok!(Tokens::transfer_allowance(Some(BOB).into(), ALICE, CHARLIE, DOT, 100));
System::assert_has_event(RuntimeEvent::Tokens(crate::Event::AllowanceSet {
currency_id: DOT,
owner: ALICE,
spender: BOB,
amount: 100,
}));
System::assert_last_event(RuntimeEvent::Tokens(crate::Event::Transfer {
currency_id: DOT,
from: ALICE,
to: CHARLIE,
amount: 100,
}));

assert_eq!(Allowances::<Runtime>::get((DOT, ALICE, BOB)), 100);
});
}

// *************************************************
// tests for inline impl
// *************************************************
Expand Down
12 changes: 12 additions & 0 deletions tokens/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ pub trait WeightInfo {
fn transfer_keep_alive() -> Weight;
fn force_transfer() -> Weight;
fn set_balance() -> Weight;
fn set_allowance() -> Weight;
fn transfer_allowance() -> Weight;
}

/// Default weights.
Expand Down Expand Up @@ -63,4 +65,14 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
fn set_allowance() -> Weight {
Weight::from_parts(34_000_000, 0)
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
fn transfer_allowance() -> Weight {
Weight::from_parts(34_000_000, 0)
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
}