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

feat: implement transfer_from and decrease_allowance #118

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
5d7aa79
feat: implement transfer_from & add integration tests
chungquantin Jul 23, 2024
1bb23d3
stable channel format
chungquantin Jul 23, 2024
4b2b9da
fix: integration test for transfer
chungquantin Jul 23, 2024
ff0f3e8
implement decrease_allowance
chungquantin Jul 23, 2024
01d713d
test: add contract integration test
chungquantin Jul 23, 2024
7eed7b4
update devnet extension
chungquantin Jul 23, 2024
149906a
Update lib.rs
chungquantin Jul 23, 2024
9d2276c
chore: benchmark approve
Daanvdplas Jul 23, 2024
2dcf5d7
fix missing data params
chungquantin Jul 23, 2024
a3443a0
fix wording
chungquantin Jul 23, 2024
3a96036
Merge branch 'daan/feat-api_fungibles_pallet' into chungquantin/feat-…
chungquantin Jul 23, 2024
48fec37
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
ece65ad
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
11bb67e
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
63cfbcf
Update pallets/api/src/fungibles/tests.rs
chungquantin Jul 24, 2024
c427c4a
Merge branch 'daan/feat-api_fungibles_pallet' into chungquantin/feat-…
chungquantin Jul 24, 2024
ecb6af0
finalize contract integration tests
chungquantin Jul 24, 2024
d6f6703
finalize contract integration tests
chungquantin Jul 24, 2024
71b96b0
refractor: remove bare_call_by
chungquantin Jul 24, 2024
f0b14a8
refractor: remove data param in transfer_from() method
chungquantin Jul 24, 2024
b77ee57
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
b8afcdd
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
3daa73e
Update pop-api/integration-tests/src/local_fungibles.rs
chungquantin Jul 24, 2024
d0ebcdf
reformat
chungquantin Jul 24, 2024
79e00dc
Merge branch 'daan/feat-api_fungibles_pallet' into chungquantin/feat-…
chungquantin Jul 26, 2024
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
11 changes: 7 additions & 4 deletions integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ use emulated_integration_tests_common::{
},
};
use frame_support::{
dispatch::RawOrigin, pallet_prelude::Weight, sp_runtime::traits::Dispatchable,
sp_runtime::DispatchResult,
dispatch::RawOrigin,
pallet_prelude::Weight,
sp_runtime::{traits::Dispatchable, DispatchResult},
};
use polkadot_runtime_parachains::assigner_on_demand;
use pop_runtime_common::Balance;
Expand Down Expand Up @@ -424,7 +425,8 @@ fn reserve_transfer_native_asset_from_system_para_to_para() {
fn reserve_transfer_native_asset_from_para_to_system_para() {
init_tracing();

// Setup: reserve transfer from AH to Pop, so that sovereign account accurate for return transfer
// Setup: reserve transfer from AH to Pop, so that sovereign account accurate for return
// transfer
let amount_to_send: Balance = ASSET_HUB_ROCOCO_ED * 1000;
fund_pop_from_system_para(
AssetHubRococoParaSender::get(),
Expand Down Expand Up @@ -488,7 +490,8 @@ fn place_coretime_spot_order_from_para_to_relay() {

let beneficiary: sp_runtime::AccountId32 = [1u8; 32].into();

// Setup: reserve transfer from relay to Pop, so that sovereign account accurate for return transfer
// Setup: reserve transfer from relay to Pop, so that sovereign account accurate for return
// transfer
let amount_to_send: Balance = pop_runtime::UNIT * 1000;
fund_pop_from_relay(RococoRelaySender::get(), amount_to_send, beneficiary.clone());

Expand Down
45 changes: 37 additions & 8 deletions pallets/api/src/fungibles/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// The fungibles pallet serves as a wrapper around the pallet_assets, offering a streamlined
/// interface for interacting with fungible assets. The goal is to provide a simplified, consistent
/// API that adheres to standards in the smart contract space.
/// interface for interacting with fungible assets. The goal is to provide a simplified,
/// consistent API that adheres to standards in the smart contract space.
pub use pallet::*;

#[cfg(test)]
Expand Down Expand Up @@ -62,8 +62,8 @@ pub mod pallet {

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Transfers `value` amount of tokens from the caller's account to account `to`, with additional
/// `data` in unspecified format.
/// Transfers `value` amount of tokens from the caller's account to account `to`, with
/// additional `data` in unspecified format.
///
/// # Arguments
/// * `id` - The ID of the asset.
Expand All @@ -84,6 +84,33 @@ pub mod pallet {
Assets::<T>::transfer_keep_alive(origin, id.into(), target, amount)
}

/// Transfers `value` amount of tokens from the delegated account approved by the `owner` to
/// account `to`, with additional `data` in unspecified format.
///
/// # Arguments
/// * `id` - The ID of the asset.
/// * `owner` - The account which previously approved for a transfer of at least `amount`
/// and
/// from which the asset balance will be withdrawn.
/// * `to` - The recipient account.
/// * `value` - The number of tokens to transfer.
///
/// # Returns
/// Returns `Ok(())` if successful, or an error if the transfer fails.
#[pallet::call_index(1)]
#[pallet::weight(AssetsWeightInfo::<T>::transfer_approved())]
pub fn transfer_from(
origin: OriginFor<T>,
id: AssetIdOf<T>,
owner: AccountIdOf<T>,
target: AccountIdOf<T>,
amount: BalanceOf<T>,
) -> DispatchResult {
let owner = T::Lookup::unlookup(owner);
let target = T::Lookup::unlookup(target);
Assets::<T>::transfer_approved(origin, id.into(), owner, target, amount)
}

/// Approves an account to spend a specified number of tokens on behalf of the caller.
///
/// # Arguments
Expand Down Expand Up @@ -119,7 +146,8 @@ pub mod pallet {
)
})?;
} else {
// If the new value is less than the current allowance, cancel the approval and set the new value
// If the new value is less than the current allowance, cancel the approval and set
// the new value
Assets::<T>::cancel_approval(origin.clone(), id.clone(), spender.clone()).map_err(
|e| {
e.with_weight(
Expand Down Expand Up @@ -167,8 +195,8 @@ pub mod pallet {
Assets::<T>::total_supply(id)
}

/// Returns the account balance for the specified `owner` for a given asset ID. Returns `0` if
/// the account is non-existent.
/// Returns the account balance for the specified `owner` for a given asset ID. Returns `0`
/// if the account is non-existent.
///
/// # Arguments
/// * `id` - The ID of the asset.
Expand Down Expand Up @@ -226,7 +254,8 @@ pub mod pallet {
/// * `id` - The ID of the asset.
///
/// # Returns
/// The number of decimals of the token as a byte vector, or an error if the operation fails.
/// The number of decimals of the token as a byte vector, or an error if the operation
/// fails.
pub fn token_decimals(id: AssetIdOf<T>) -> u8 {
<Assets<T> as MetadataInspect<AccountIdOf<T>>>::decimals(id)
}
Expand Down
46 changes: 45 additions & 1 deletion pallets/api/src/fungibles/tests.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
use crate::mock::*;
use frame_support::{
assert_ok,
assert_noop, assert_ok,
traits::fungibles::{approvals::Inspect, metadata::Inspect as MetadataInspect},
};
use sp_runtime::{DispatchError, ModuleError};

const ASSET: u32 = 42;

fn get_dispatch_error(index: u8, error_index: u8, error_message: &'static str) -> DispatchError {
DispatchError::Module(ModuleError {
index,
error: [error_index, 0, 0, 0],
message: Some(error_message),
})
}

#[test]
fn transfer_works() {
new_test_ext().execute_with(|| {
Expand All @@ -18,6 +27,41 @@ fn transfer_works() {
});
}

#[test]
fn transfer_from_works() {
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
new_test_ext().execute_with(|| {
let amount: Balance = 100 * UNIT;
// Approve CHARLIE to transfer up to `amount` to BOB
create_asset_mint_and_approve(ALICE, ASSET, ALICE, amount * 2, CHARLIE, amount / 2);

let transferred = amount / 2;

assert_eq!(transferred, Assets::allowance(ASSET, &ALICE, &CHARLIE));
assert_eq!(0, Assets::allowance(ASSET, &ALICE, &BOB));

// Transfer `amount` from an unapproved spender
assert_noop!(
Fungibles::transfer_from(signed(BOB), ASSET, ALICE, BOB, transferred),
get_dispatch_error(1, 10, "Unapproved")
);

// Transfer `amount` more than the allowed allowance
assert_noop!(
Fungibles::transfer_from(signed(CHARLIE), ASSET, ALICE, BOB, amount),
get_dispatch_error(1, 10, "Unapproved")
);

let alice_balance_before_transfer = Assets::balance(ASSET, &ALICE);
let bob_balance_before_transfer = Assets::balance(ASSET, &BOB);
assert_ok!(Fungibles::transfer_from(signed(CHARLIE), ASSET, ALICE, BOB, transferred));
let alice_balance_after_transfer = Assets::balance(ASSET, &ALICE);
let bob_balance_after_transfer = Assets::balance(ASSET, &BOB);
// Check that BOB receives the `amount` and ALICE `amount` is spent successfully by CHARLIE
assert_eq!(bob_balance_after_transfer, bob_balance_before_transfer + transferred);
assert_eq!(alice_balance_after_transfer, alice_balance_before_transfer - transferred);
});
}

// Non-additive, sets new value.
#[test]
fn approve_works() {
Expand Down
9 changes: 8 additions & 1 deletion pallets/api/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ impl crate::fungibles::Config for Test {

pub(crate) const ALICE: AccountId = 1;
pub(crate) const BOB: AccountId = 2;
pub(crate) const CHARLIE: AccountId = 3;
pub(crate) const DAVE: AccountId = 4;
chungquantin marked this conversation as resolved.
Show resolved Hide resolved
pub(crate) const INIT_AMOUNT: Balance = 100_000_000 * UNIT;
pub(crate) const UNIT: Balance = 10_000_000_000;

Expand All @@ -111,7 +113,12 @@ pub(crate) fn new_test_ext() -> sp_io::TestExternalities {
.expect("Frame system builds valid default genesis config");

pallet_balances::GenesisConfig::<Test> {
balances: vec![(ALICE, INIT_AMOUNT), (BOB, INIT_AMOUNT)],
balances: vec![
(ALICE, INIT_AMOUNT),
(BOB, INIT_AMOUNT),
(CHARLIE, INIT_AMOUNT),
(DAVE, INIT_AMOUNT),
],
}
.assimilate_storage(&mut t)
.expect("Pallet balances storage can be assimilated");
Expand Down
71 changes: 71 additions & 0 deletions pop-api/integration-tests/src/local_fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ fn transfer(
result
}

fn transfer_from(
addr: AccountId32,
asset_id: AssetId,
from: AccountId32,
to: AccountId32,
value: Balance,
) -> ExecReturnValue {
let function = function_selector("transfer_from");
let params = [function, asset_id.encode(), from.encode(), to.encode(), value.encode()].concat();
let result = bare_call(addr, params, 0).expect("should work");
result
}

fn approve(
addr: AccountId32,
asset_id: AssetId,
Expand Down Expand Up @@ -367,6 +380,64 @@ fn transfer_works() {
});
}

#[test]
fn transfer_from_works() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we add test situations for AssetNotLive. See other tests how to (freeze and destroy)

new_test_ext().execute_with(|| {
let _ = env_logger::try_init();
let addr = instantiate("contracts/fungibles/target/ink/fungibles.wasm", INIT_VALUE, vec![]);
let amount: Balance = 100 * UNIT;
// TODO: Add approval process and finalize the integration test
// Asset does not exist.
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), 1, ALICE, BOB, amount,)),
Module { index: 52, error: 3 },
);
// Create asset with Alice as owner and mint `amount` to contract address.
let asset = create_asset_and_mint_to(ALICE, 1, addr.clone(), amount);
// Asset is not live, i.e. frozen or being destroyed.
freeze_asset(ALICE, asset);
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), asset, ALICE, BOB, amount,)),
Module { index: 52, error: 16 },
);
thaw_asset(ALICE, asset);
// Not enough balance.
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), asset, ALICE, BOB, amount + 1 * UNIT)),
Module { index: 52, error: 0 },
);
// Not enough balance due to ED.
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), asset, ALICE, BOB, amount)),
Module { index: 52, error: 0 },
);
// Successful transfer.
let alice_balance_before_transfer = Assets::balance(asset, &ALICE);
let bob_balance_before_transfer = Assets::balance(asset, &BOB);

let result = transfer_from(addr.clone(), asset, ALICE, BOB, amount / 2);
assert!(!result.did_revert(), "Contract reverted!");

let alice_balance_after_transfer = Assets::balance(asset, &BOB);
let bob_balance_after_transfer = Assets::balance(asset, &BOB);

assert_eq!(bob_balance_after_transfer, bob_balance_before_transfer + amount / 2);
assert_eq!(alice_balance_after_transfer, alice_balance_before_transfer - amount / 2);

// Transfer asset to account that does not exist.
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), asset, ALICE, FERDIE, amount / 4)),
Token(CannotCreate)
);
// Asset is not live, i.e. frozen or being destroyed.
start_destroy_asset(ALICE, asset);
assert_eq!(
decoded::<Error>(transfer_from(addr.clone(), asset, ALICE, BOB, amount / 4)),
Module { index: 52, error: 16 },
);
});
}

#[test]
fn approve_works() {
new_test_ext().execute_with(|| {
Expand Down
23 changes: 13 additions & 10 deletions primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ pub mod v0 {
#[allow(clippy::unnecessary_cast)]
pub enum Error {
/// An unknown error occurred. This variant captures any unexpected errors that the
/// contract cannot specifically handle. It is useful for cases where there are breaking
/// changes in the runtime or when an error falls outside the predefined categories. The
/// variant includes:
/// contract cannot specifically handle. It is useful for cases where there are
/// breaking changes in the runtime or when an error falls outside the predefined
/// categories. The variant includes:
///
/// - `dispatch_error_index`: The index within the `DispatchError`.
/// - `error_index`: The index within the `DispatchError` variant (e.g. a `TokenError`).
/// - `error_index`: The index within the `DispatchError` variant (e.g. a
/// `TokenError`).
/// - `error`: The specific error code or sub-index, providing additional context (e.g.
/// `error` in `ModuleError`).
Other { dispatch_error_index: u8, error_index: u8, error: u8 } = 0,
Expand All @@ -48,14 +49,16 @@ pub mod v0 {
Token(TokenError) = 7,
/// An arithmetic error.
Arithmetic(ArithmeticError) = 8,
/// The number of transactional layers has been reached, or we are not in a transactional
/// layer.
/// The number of transactional layers has been reached, or we are not in a
/// transactional layer.
Transactional(TransactionalError) = 9,
/// Resources exhausted, e.g. attempt to read/write data which is too large to manipulate.
/// Resources exhausted, e.g. attempt to read/write data which is too large to
/// manipulate.
Exhausted = 10,
/// The state is corrupt; this is generally not going to fix itself.
Corruption = 11,
/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later.
/// Some resource (e.g. a preimage) is unavailable right now. This might fix itself
/// later.
Unavailable = 12,
/// Root origin is not allowed.
RootNotAllowed = 13,
Expand Down Expand Up @@ -83,8 +86,8 @@ pub mod v0 {
pub enum TokenError {
/// Funds are unavailable.
FundsUnavailable,
/// Some part of the balance gives the only provider reference to the account and thus cannot
/// be (re)moved.
/// Some part of the balance gives the only provider reference to the account and thus
/// cannot be (re)moved.
OnlyProvider,
/// Account cannot exist with the funds that would be given.
BelowMinimum,
Expand Down
Loading
Loading