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

fix origin of erc20 xtokens transfer #2180

Merged
merged 15 commits into from
Jun 8, 2022
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions modules/currencies/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = ["Acala Developers"]
edition = "2021"

[dependencies]
log = { version = "0.4.17", default-features = false }
serde = { version = "1.0.136", optional = true }
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
scale-info = { version = "2.1", default-features = false, features = ["derive"] }
Expand Down Expand Up @@ -37,6 +38,7 @@ module-evm-bridge = { path = "../evm-bridge" }
default = ["std"]
std = [
"serde",
"log/std",
"codec/std",
"scale-info/std",
"sp-core/std",
Expand Down
164 changes: 141 additions & 23 deletions modules/currencies/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use frame_support::{
traits::{
tokens::{fungible, fungibles, DepositConsequence, WithdrawConsequence},
BalanceStatus as Status, Currency as PalletCurrency, ExistenceRequirement, Get, Imbalance,
LockableCurrency as PalletLockableCurrency, ReservableCurrency as PalletReservableCurrency, WithdrawReasons,
LockableCurrency as PalletLockableCurrency, NamedReservableCurrency,
ReservableCurrency as PalletReservableCurrency, WithdrawReasons,
},
transactional,
};
Expand All @@ -37,9 +38,10 @@ use orml_traits::{
arithmetic::{Signed, SimpleArithmetic},
currency::TransferAll,
BalanceStatus, BasicCurrency, BasicCurrencyExtended, BasicLockableCurrency, BasicReservableCurrency,
LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency, OnDust,
LockIdentifier, MultiCurrency, MultiCurrencyExtended, MultiLockableCurrency, MultiReservableCurrency,
NamedBasicReservableCurrency, OnDust,
};
use primitives::{evm::EvmAddress, CurrencyId};
use primitives::{evm::EvmAddress, CurrencyId, ReserveIdentifier};
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{CheckedAdd, CheckedSub, Convert, MaybeSerializeDeserialize, Saturating, StaticLookup, Zero},
Expand Down Expand Up @@ -79,6 +81,7 @@ pub mod module {
type NativeCurrency: BasicCurrencyExtended<Self::AccountId, Balance = BalanceOf<Self>, Amount = AmountOf<Self>>
+ BasicLockableCurrency<Self::AccountId, Balance = BalanceOf<Self>>
+ BasicReservableCurrency<Self::AccountId, Balance = BalanceOf<Self>>
+ NamedBasicReservableCurrency<Self::AccountId, ReserveIdentifier>
+ fungible::Inspect<Self::AccountId, Balance = BalanceOf<Self>>
+ fungible::Mutate<Self::AccountId, Balance = BalanceOf<Self>>
+ fungible::Transfer<Self::AccountId, Balance = BalanceOf<Self>>
Expand Down Expand Up @@ -145,6 +148,10 @@ pub mod module {
},
}

#[pallet::storage]
#[pallet::getter(fn sibling_reserve_storage_fee)]
pub type SiblingReserveStorageFee<T: Config> = StorageValue<_, (T::AccountId, BalanceOf<T>), OptionQuery>;

#[pallet::pallet]
pub struct Pallet<T>(_);

Expand Down Expand Up @@ -183,7 +190,7 @@ pub mod module {
) -> DispatchResult {
let from = ensure_signed(origin)?;
let to = T::Lookup::lookup(dest)?;
T::NativeCurrency::transfer(&from, &to, amount)
<T::NativeCurrency as BasicCurrency<T::AccountId>>::transfer(&from, &to, amount)
}

/// Update amount of account `who` under `currency_id`.
Expand Down Expand Up @@ -272,7 +279,9 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
}
Default::default()
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::total_balance(who),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::total_balance(who)
}
_ => T::MultiCurrency::total_balance(currency_id, who),
}
}
Expand All @@ -290,7 +299,9 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
}
Default::default()
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::free_balance(who),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::free_balance(who)
}
_ => T::MultiCurrency::free_balance(currency_id, who),
}
}
Expand All @@ -315,7 +326,9 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
ensure!(balance >= amount, Error::<T>::BalanceTooLow);
Ok(())
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::ensure_can_withdraw(who, amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::ensure_can_withdraw(who, amount)
}
_ => T::MultiCurrency::ensure_can_withdraw(currency_id, who, amount),
}
}
Expand All @@ -333,9 +346,30 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
match currency_id {
CurrencyId::Erc20(contract) => {
let sender = T::AddressMapping::get_evm_address(from).ok_or(Error::<T>::EvmAccountNotFound)?;
let origin = T::EVMBridge::get_origin().ok_or(Error::<T>::RealOriginNotFound)?;
let origin_address = T::AddressMapping::get_or_create_evm_address(&origin);
let erc20_holding_account = T::AddressMapping::get_account_id(&T::Erc20HoldingAccount::get());
let origin_address = if erc20_holding_account == to.clone() {
// withdraw **from** sender to erc20 holding account. in xcm case which receive erc20 from sibling
// parachain, sender is sibling parachain sovereign account. As the origin here is used to charge
// storage fee, we must make sure sibling parachain sovereign account has enough token to charge
// storage fee.
T::AddressMapping::get_or_create_evm_address(from)
} else if erc20_holding_account == from.clone() {
zqhxuyuan marked this conversation as resolved.
Show resolved Hide resolved
// deposit from erc20 holding account **to** receiver. the same as xcm case above, but we choose
// receiver to charge storage fee.
ensure!(
!Self::free_balance(currency_id, &erc20_holding_account).is_zero(),
Error::<T>::RealOriginNotFound
);
T::AddressMapping::get_or_create_evm_address(to)
} else {
// local or xtokens erc20 transfer
let origin = T::EVMBridge::get_origin().ok_or(Error::<T>::RealOriginNotFound)?;
T::AddressMapping::get_or_create_evm_address(&origin)
};
let address = T::AddressMapping::get_or_create_evm_address(to);
let contract_acc = T::AddressMapping::get_account_id(&contract);
let reserve_pre =
T::NativeCurrency::reserved_balance_named(&ReserveIdentifier::EvmStorageDeposit, &contract_acc);
T::EVMBridge::transfer(
InvokeContext {
contract,
Expand All @@ -345,8 +379,28 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
address,
amount,
)?;
let reserve_post =
T::NativeCurrency::reserved_balance_named(&ReserveIdentifier::EvmStorageDeposit, &contract_acc);
// In xcm case which receive erc20 from sibling parachain, withdraw from sibling parachain account
// make it cost some native token to charge storage fee, as evm must reserve storage fee. in order
// to keep sibling parachain account at a lost of native token, we use deposit recipient to refund
// native token that was expended back to sibling parachain account.
// In xcm-v3, XcmContext can be used to do message passing between xcm instructions.
// Current, we're use extra temporary storage to record sibling parachain storage fee.
if erc20_holding_account == to.clone() && reserve_post > reserve_pre {
// after withdraw asset from sibling parachain account, record the expended storage fee.
SiblingReserveStorageFee::<T>::put((from.clone(), reserve_post.saturating_sub(reserve_pre)));
} else if erc20_holding_account == from.clone() {
// after deposit asset to recipient, refund native token to sibling parachain account, and clear the
// storage.
if let Some((sibling, reserve_storage)) = SiblingReserveStorageFee::<T>::take() {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::transfer(to, &sibling, reserve_storage)?;
}
}
}
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::transfer(from, to, amount)?
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::transfer(from, to, amount)?,
_ => T::MultiCurrency::transfer(currency_id, from, to, amount)?,
}

Expand All @@ -370,7 +424,9 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
who,
amount,
),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::deposit(who, amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::deposit(who, amount)
}
_ => T::MultiCurrency::deposit(currency_id, who, amount),
}
}
Expand All @@ -387,23 +443,29 @@ impl<T: Config> MultiCurrency<T::AccountId> for Pallet<T> {
&T::AddressMapping::get_account_id(&T::Erc20HoldingAccount::get()),
amount,
),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::withdraw(who, amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::withdraw(who, amount)
}
_ => T::MultiCurrency::withdraw(currency_id, who, amount),
}
}

fn can_slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> bool {
match currency_id {
CurrencyId::Erc20(_) => amount.is_zero(),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::can_slash(who, amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::can_slash(who, amount)
}
_ => T::MultiCurrency::can_slash(currency_id, who, amount),
}
}

fn slash(currency_id: Self::CurrencyId, who: &T::AccountId, amount: Self::Balance) -> Self::Balance {
match currency_id {
CurrencyId::Erc20(_) => Default::default(),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::slash(who, amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::slash(who, amount)
}
_ => T::MultiCurrency::slash(currency_id, who, amount),
}
}
Expand Down Expand Up @@ -469,15 +531,19 @@ impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
fn can_reserve(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> bool {
match currency_id {
CurrencyId::Erc20(_) => Self::ensure_can_withdraw(currency_id, who, value).is_ok(),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::can_reserve(who, value),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::can_reserve(who, value)
}
_ => T::MultiCurrency::can_reserve(currency_id, who, value),
}
}

fn slash_reserved(currency_id: Self::CurrencyId, who: &T::AccountId, value: Self::Balance) -> Self::Balance {
match currency_id {
CurrencyId::Erc20(_) => value,
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::slash_reserved(who, value),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::slash_reserved(who, value)
}
_ => T::MultiCurrency::slash_reserved(currency_id, who, value),
}
}
Expand All @@ -498,7 +564,9 @@ impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
}
Default::default()
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::reserved_balance(who),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::reserved_balance(who)
}
_ => T::MultiCurrency::reserved_balance(currency_id, who),
}
}
Expand All @@ -518,9 +586,12 @@ impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
},
reserve_address(address),
value,
)
)?;
Ok(())
}
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::reserve(who, value)
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::reserve(who, value),
_ => T::MultiCurrency::reserve(currency_id, who, value),
}
}
Expand Down Expand Up @@ -559,7 +630,9 @@ impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
value
}
}
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::unreserve(who, value),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::unreserve(who, value)
}
_ => T::MultiCurrency::unreserve(currency_id, who, value),
}
}
Expand Down Expand Up @@ -625,7 +698,12 @@ impl<T: Config> MultiReservableCurrency<T::AccountId> for Pallet<T> {
Ok(value - actual)
}
id if id == T::GetNativeCurrencyId::get() => {
T::NativeCurrency::repatriate_reserved(slashed, beneficiary, value, status)
<T::NativeCurrency as BasicReservableCurrency<T::AccountId>>::repatriate_reserved(
slashed,
beneficiary,
value,
status,
)
}
_ => T::MultiCurrency::repatriate_reserved(currency_id, slashed, beneficiary, value, status),
}
Expand Down Expand Up @@ -1305,6 +1383,40 @@ where
}
}

impl<T, AccountId, Currency, Amount, Moment, ReserveIdentifier>
NamedBasicReservableCurrency<AccountId, ReserveIdentifier> for BasicCurrencyAdapter<T, Currency, Amount, Moment>
where
Currency: NamedReservableCurrency<AccountId, ReserveIdentifier = ReserveIdentifier>,
T: Config,
{
fn slash_reserved_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> Self::Balance {
let (_, gap) = Currency::slash_reserved_named(id, who, value);
gap
}

fn reserved_balance_named(id: &ReserveIdentifier, who: &AccountId) -> Self::Balance {
Currency::reserved_balance_named(id, who)
}

fn reserve_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> DispatchResult {
Currency::reserve_named(id, who, value)
}

fn unreserve_named(id: &ReserveIdentifier, who: &AccountId, value: Self::Balance) -> Self::Balance {
Currency::unreserve_named(id, who, value)
}

fn repatriate_reserved_named(
id: &ReserveIdentifier,
slashed: &AccountId,
beneficiary: &AccountId,
value: Self::Balance,
status: BalanceStatus,
) -> result::Result<Self::Balance, DispatchError> {
Currency::repatriate_reserved_named(id, slashed, beneficiary, value, status)
}
}

type FungibleBalanceOf<A, Currency> = <Currency as fungible::Inspect<A>>::Balance;

impl<T, AccountId, Currency, Amount, Moment> fungible::Inspect<AccountId>
Expand Down Expand Up @@ -1425,7 +1537,11 @@ impl<T: Config> TransferAll<T::AccountId> for Pallet<T> {
T::MultiCurrency::transfer_all(source, dest)?;

// transfer all free to dest
T::NativeCurrency::transfer(source, dest, T::NativeCurrency::free_balance(source))
<T::NativeCurrency as BasicCurrency<T::AccountId>>::transfer(
source,
dest,
<T::NativeCurrency as BasicCurrency<T::AccountId>>::free_balance(source),
)
}
}

Expand All @@ -1445,7 +1561,9 @@ where
// if failed will leave some dust which still could be recycled.
let _ = match currency_id {
CurrencyId::Erc20(_) => Ok(()),
id if id == T::GetNativeCurrencyId::get() => T::NativeCurrency::transfer(who, &GetAccountId::get(), amount),
id if id == T::GetNativeCurrencyId::get() => {
<T::NativeCurrency as BasicCurrency<T::AccountId>>::transfer(who, &GetAccountId::get(), amount)
}
_ => T::MultiCurrency::transfer(currency_id, who, &GetAccountId::get(), amount),
};
}
Expand Down
2 changes: 2 additions & 0 deletions modules/currencies/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,8 @@ fn fungible_mutate_trait_should_work() {
&alice(),
0
));
// mint_into will deposit erc20 holding account to recipient.
// but here erc20 holding account don't have enough balance.
assert_noop!(
<Currencies as fungibles::Mutate<_>>::mint_into(CurrencyId::Erc20(erc20_address()), &alice(), 1),
Error::<Runtime>::RealOriginNotFound
Expand Down
4 changes: 4 additions & 0 deletions modules/evm-bridge/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,10 @@ impl<T: Config> EVMBridgeTrait<AccountIdOf<T>, BalanceOf<T>> for EVMBridge<T> {
fn set_origin(origin: AccountIdOf<T>) {
T::EVM::set_origin(origin);
}

fn clear_origin() {
T::EVM::clear_origin();
}
}

impl<T: Config> Pallet<T> {
Expand Down
6 changes: 6 additions & 0 deletions modules/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1611,6 +1611,8 @@ impl<T: Config> Pallet<T> {
);

T::ChargeTransactionPayment::reserve_fee(&user, amount, Some(RESERVE_ID_STORAGE_DEPOSIT))?;
let reserved = T::Currency::reserved_balance_named(&RESERVE_ID_STORAGE_DEPOSIT, &user);
log::info!(target: "evm", "reserved amount:{:?}", reserved);
Ok(())
}

Expand Down Expand Up @@ -1774,6 +1776,10 @@ impl<T: Config> EVMTrait<T::AccountId> for Pallet<T> {
fn set_origin(origin: T::AccountId) {
ExtrinsicOrigin::<T>::set(Some(origin));
}

fn clear_origin() {
ExtrinsicOrigin::<T>::kill();
}
zqhxuyuan marked this conversation as resolved.
Show resolved Hide resolved
}

pub struct EvmChainId<T>(PhantomData<T>);
Expand Down
Loading