Skip to content

Commit

Permalink
feat: track pending payments to MBSM (#851)
Browse files Browse the repository at this point in the history
* feat: track pending payments to MBSM

* feat: send payments to MBSM

* fix: add tests

* fix: add tests for refunds
  • Loading branch information
shekohex authored Dec 13, 2024
1 parent 21df6e1 commit af5234c
Show file tree
Hide file tree
Showing 7 changed files with 442 additions and 37 deletions.
3 changes: 1 addition & 2 deletions pallets/services/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -968,11 +968,10 @@ impl<T: Config> Pallet<T> {
/// Moves a `value` amount of tokens from the caller's account to `to`.
pub fn erc20_transfer(
erc20: H160,
caller: &T::AccountId,
from: H160,
to: H160,
value: BalanceOf<T>,
) -> Result<(bool, Weight), DispatchErrorWithPostInfo> {
let from = T::EvmAddressMapping::into_address(caller.clone());
#[allow(deprecated)]
let transfer_fn = Function {
name: String::from("transfer"),
Expand Down
124 changes: 120 additions & 4 deletions pallets/services/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub mod module {
use sp_runtime::Percent;
use sp_std::vec::Vec;
use tangle_primitives::services::MasterBlueprintServiceManagerRevision;
use tangle_primitives::{services::*, MultiAssetDelegationInfo};
use tangle_primitives::{services::*, Account, MultiAssetDelegationInfo};
use types::*;

#[pallet::config]
Expand Down Expand Up @@ -291,6 +291,12 @@ pub mod module {
MaxMasterBlueprintServiceManagerVersionsExceeded,
/// The ERC20 transfer failed.
ERC20TransferFailed,
/// Missing EVM Origin for the EVM execution.
MissingEVMOrigin,
/// Expected the account to be an EVM address.
ExpectedEVMAddress,
/// Expected the account to be an account ID.
ExpectedAccountId,
}

#[pallet::event]
Expand Down Expand Up @@ -622,6 +628,15 @@ pub mod module {
OperatorProfile<T::Constraints>,
ResultQuery<Error<T>::OperatorProfileNotFound>,
>;
/// Holds the service payment information for a service request.
/// Once the service is initiated, the payment is transferred to the MBSM and this
/// information is removed.
///
/// Service Requst ID -> Service Payment
#[pallet::storage]
#[pallet::getter(fn service_payment)]
pub type StagingServicePayments<T: Config> =
StorageMap<_, Identity, u64, StagingServicePayment<T::AccountId, T::AssetId, BalanceOf<T>>>;

#[pallet::call]
impl<T: Config> Pallet<T> {
Expand Down Expand Up @@ -846,6 +861,7 @@ pub mod module {
#[pallet::weight(T::WeightInfo::request())]
pub fn request(
origin: OriginFor<T>,
evm_origin: Option<H160>,
#[pallet::compact] blueprint_id: u64,
permitted_callers: Vec<T::AccountId>,
operators: Vec<T::AccountId>,
Expand All @@ -871,10 +887,11 @@ pub mod module {
}

let mut native_value = Zero::zero();
let request_id = NextServiceRequestId::<T>::get();

if value != Zero::zero() {
// Payment transfer
match payment_asset {
let refund_to = match payment_asset {
// Handle the case of native currency.
Asset::Custom(asset_id) if asset_id == Zero::zero() => {
T::Currency::transfer(
Expand All @@ -884,6 +901,7 @@ pub mod module {
ExistenceRequirement::KeepAlive,
)?;
native_value = value;
Account::id(caller.clone())
},
Asset::Custom(asset_id) => {
T::Fungibles::transfer(
Expand All @@ -893,16 +911,31 @@ pub mod module {
value,
Preservation::Preserve,
)?;
Account::id(caller.clone())
},
Asset::Erc20(token) => {
// origin check.
let evm_origin = evm_origin.ok_or(Error::<T>::MissingEVMOrigin)?;
let mapped_origin = T::EvmAddressMapping::into_account_id(evm_origin);
ensure!(mapped_origin == caller, DispatchError::BadOrigin);
let (success, _weight) =
Self::erc20_transfer(token, &caller, Self::address(), value)?;
Self::erc20_transfer(token, evm_origin, Self::address(), value)?;
ensure!(success, Error::<T>::ERC20TransferFailed);
Account::from(evm_origin)
},
};

// Save the payment information for the service request.
let payment = StagingServicePayment {
request_id,
refund_to,
asset: payment_asset,
amount: value,
};

StagingServicePayments::<T>::insert(request_id, payment);
}

let request_id = NextServiceRequestId::<T>::get();
let (allowed, _weight) = Self::on_request_hook(
&blueprint,
blueprint_id,
Expand Down Expand Up @@ -1068,6 +1101,44 @@ pub mod module {
.map_err(|_| Error::<T>::MaxServicesPerUserExceeded)
})?;

// Payment
if let Some(payment) = Self::service_payment(request_id) {
// send payments to the MBSM
let mbsm_address = Self::mbsm_address_of(&blueprint)?;
let mbsm_account_id = T::EvmAddressMapping::into_account_id(mbsm_address);
match payment.asset {
Asset::Custom(asset_id) if asset_id == Zero::zero() => {
T::Currency::transfer(
&Self::account_id(),
&mbsm_account_id,
payment.amount,
ExistenceRequirement::AllowDeath,
)?;
},
Asset::Custom(asset_id) => {
T::Fungibles::transfer(
asset_id,
&Self::account_id(),
&mbsm_account_id,
payment.amount,
Preservation::Expendable,
)?;
},
Asset::Erc20(token) => {
let (success, _weight) = Self::erc20_transfer(
token,
Self::address(),
mbsm_address,
payment.amount,
)?;
ensure!(success, Error::<T>::ERC20TransferFailed);
},
}

// Remove the payment information.
StagingServicePayments::<T>::remove(request_id);
}

let (allowed, _weight) = Self::on_service_init_hook(
&blueprint,
blueprint_id,
Expand Down Expand Up @@ -1131,6 +1202,51 @@ pub mod module {
request_id,
});

// Refund the payment
if let Some(payment) = Self::service_payment(request_id) {
match payment.asset {
Asset::Custom(asset_id) if asset_id == Zero::zero() => {
let refund_to = payment
.refund_to
.try_into_account_id()
.map_err(|_| Error::<T>::ExpectedAccountId)?;
T::Currency::transfer(
&Self::account_id(),
&refund_to,
payment.amount,
ExistenceRequirement::AllowDeath,
)?;
},
Asset::Custom(asset_id) => {
let refund_to = payment
.refund_to
.try_into_account_id()
.map_err(|_| Error::<T>::ExpectedAccountId)?;
T::Fungibles::transfer(
asset_id,
&Self::account_id(),
&refund_to,
payment.amount,
Preservation::Expendable,
)?;
},
Asset::Erc20(token) => {
let refund_to = payment
.refund_to
.try_into_address()
.map_err(|_| Error::<T>::ExpectedEVMAddress)?;
let (success, _weight) = Self::erc20_transfer(
token,
Self::address(),
refund_to,
payment.amount,
)?;
ensure!(success, Error::<T>::ERC20TransferFailed);
},
}
StagingServicePayments::<T>::remove(request_id);
}

// TODO: make use of the returned weight from the hook.
Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes })
}
Expand Down
29 changes: 23 additions & 6 deletions pallets/services/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,7 @@ impl EvmAddressMapping<AccountId> for PalletEVMAddressMapping {
}

fn into_address(account_id: AccountId) -> H160 {
account_id.using_encoded(|b| {
let mut addr = [0u8; 20];
addr.copy_from_slice(&b[0..20]);
H160(addr)
})
H160::from_slice(&AsRef::<[u8; 32]>::as_ref(&account_id)[0..20])
}
}

Expand Down Expand Up @@ -473,7 +469,16 @@ pub fn mock_pub_key(id: u8) -> AccountId {
}

pub fn mock_address(id: u8) -> H160 {
H160([id; 20])
H160::from_slice(&[id; 20])
}

pub fn account_id_to_address(account_id: AccountId) -> H160 {
H160::from_slice(&AsRef::<[u8; 32]>::as_ref(&account_id)[0..20])
}

pub fn address_to_account_id(address: H160) -> AccountId {
use pallet_evm::AddressMapping;
<Runtime as pallet_evm::Config>::AddressMapping::into_account_id(address)
}

pub fn mock_authorities(vec: Vec<u8>) -> Vec<AccountId> {
Expand Down Expand Up @@ -569,6 +574,18 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<AccountId>) -> sp_io::TestE
);
}

for a in &authorities {
evm_accounts.insert(
account_id_to_address(a.clone()),
fp_evm::GenesisAccount {
code: vec![],
storage: Default::default(),
nonce: Default::default(),
balance: Uint::from(1_000).mul(Uint::from(10).pow(Uint::from(18))),
},
);
}

let evm_config =
pallet_evm::GenesisConfig::<Runtime> { accounts: evm_accounts, ..Default::default() };

Expand Down
Loading

0 comments on commit af5234c

Please sign in to comment.