#![cfg_attr(not(feature = "std"), no_std)]
use fp_evm::{ExitError, PrecompileHandle};
use frame_support::{
dispatch::{GetDispatchInfo, PostDispatchInfo},
traits::{
fungibles::{
approvals::Inspect as ApprovalInspect, metadata::Inspect as MetadataInspect, Inspect,
},
OriginTrait,
},
DefaultNoBound,
};
use pallet_evm::AddressMapping;
use precompile_utils::prelude::*;
use sp_runtime::traits::{Bounded, Dispatchable, StaticLookup};
use sp_core::{Get, MaxEncodedLen, H160, U256};
use sp_std::{
convert::{TryFrom, TryInto},
marker::PhantomData,
};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub const SELECTOR_LOG_TRANSFER: [u8; 32] = keccak256!("Transfer(address,address,uint256)");
pub const SELECTOR_LOG_APPROVAL: [u8; 32] = keccak256!("Approval(address,address,uint256)");
pub type BalanceOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::Balance;
pub type AssetIdOf<Runtime, Instance = ()> = <Runtime as pallet_assets::Config<Instance>>::AssetId;
pub trait AddressToAssetId<AssetId> {
fn address_to_asset_id(address: H160) -> Option<AssetId>;
fn asset_id_to_address(asset_id: AssetId) -> H160;
}
#[derive(Clone, DefaultNoBound)]
pub struct Erc20AssetsPrecompileSet<Runtime, Instance: 'static = ()>(
PhantomData<(Runtime, Instance)>,
);
impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance> {
pub fn new() -> Self {
Self(PhantomData)
}
}
#[precompile_utils::precompile]
#[precompile::precompile_set]
#[precompile::test_concrete_types(mock::Runtime, ())]
impl<Runtime, Instance> Erc20AssetsPrecompileSet<Runtime, Instance>
where
Instance: 'static,
Runtime: pallet_assets::Config<Instance> + pallet_evm::Config + frame_system::Config,
Runtime::RuntimeCall: Dispatchable<PostInfo = PostDispatchInfo> + GetDispatchInfo,
Runtime::RuntimeCall: From<pallet_assets::Call<Runtime, Instance>>,
<Runtime::RuntimeCall as Dispatchable>::RuntimeOrigin: From<Option<Runtime::AccountId>>,
BalanceOf<Runtime, Instance>: TryFrom<U256> + Into<U256> + solidity::Codec,
Runtime: AddressToAssetId<AssetIdOf<Runtime, Instance>>,
<<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin: OriginTrait,
AssetIdOf<Runtime, Instance>: Copy,
{
#[precompile::discriminant]
fn discriminant(address: H160, gas: u64) -> DiscriminantResult<AssetIdOf<Runtime, Instance>> {
let extra_cost = RuntimeHelper::<Runtime>::db_read_gas_cost();
if gas < extra_cost {
return DiscriminantResult::OutOfGas;
}
let asset_id = match Runtime::address_to_asset_id(address) {
Some(asset_id) => asset_id,
None => return DiscriminantResult::None(extra_cost),
};
if pallet_assets::Pallet::<Runtime, Instance>::maybe_total_supply(asset_id).is_some() {
DiscriminantResult::Some(asset_id, extra_cost)
} else {
DiscriminantResult::None(extra_cost)
}
}
#[precompile::public("totalSupply()")]
#[precompile::view]
fn total_supply(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
) -> EvmResult<U256> {
handle.record_db_read::<Runtime>(223)?;
Ok(pallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id).into())
}
#[precompile::public("balanceOf(address)")]
#[precompile::view]
fn balance_of(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
who: Address,
) -> EvmResult<U256> {
handle.record_db_read::<Runtime>(
99 + <Runtime as pallet_assets::Config<Instance>>::Extra::max_encoded_len(),
)?;
let who: H160 = who.into();
let amount: U256 = {
let who: Runtime::AccountId = Runtime::AddressMapping::into_account_id(who);
pallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, &who).into()
};
Ok(amount)
}
#[precompile::public("allowance(address,address)")]
#[precompile::view]
fn allowance(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
owner: Address,
spender: Address,
) -> EvmResult<U256> {
handle.record_db_read::<Runtime>(148)?;
let owner: H160 = owner.into();
let spender: H160 = spender.into();
let amount: U256 = {
let owner: Runtime::AccountId = Runtime::AddressMapping::into_account_id(owner);
let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id, &owner, &spender).into()
};
Ok(amount)
}
#[precompile::public("approve(address,uint256)")]
fn approve(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
spender: Address,
value: U256,
) -> EvmResult<bool> {
handle.record_log_costs_manual(3, 32)?;
let spender: H160 = spender.into();
Self::approve_inner(asset_id, handle, handle.context().caller, spender, value)?;
log3(
handle.context().address,
SELECTOR_LOG_APPROVAL,
handle.context().caller,
spender,
solidity::encode_event_data(value),
)
.record(handle)?;
Ok(true)
}
fn approve_inner(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
owner: H160,
spender: H160,
value: U256,
) -> EvmResult {
let owner = Runtime::AddressMapping::into_account_id(owner);
let spender: Runtime::AccountId = Runtime::AddressMapping::into_account_id(spender);
let amount: BalanceOf<Runtime, Instance> =
value.try_into().unwrap_or_else(|_| Bounded::max_value());
handle.record_db_read::<Runtime>(148)?;
if pallet_assets::Pallet::<Runtime, Instance>::allowance(asset_id, &owner, &spender)
!= 0u32.into()
{
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(owner.clone()).into(),
pallet_assets::Call::<Runtime, Instance>::cancel_approval {
id: asset_id.into(),
delegate: Runtime::Lookup::unlookup(spender.clone()),
},
)?;
}
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(owner).into(),
pallet_assets::Call::<Runtime, Instance>::approve_transfer {
id: asset_id.into(),
delegate: Runtime::Lookup::unlookup(spender),
amount,
},
)?;
Ok(())
}
#[precompile::public("transfer(address,uint256)")]
fn transfer(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
to: Address,
value: U256,
) -> EvmResult<bool> {
handle.record_log_costs_manual(3, 32)?;
let to: H160 = to.into();
let value = Self::u256_to_amount(value).in_field("value")?;
{
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let to = Runtime::AddressMapping::into_account_id(to);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_assets::Call::<Runtime, Instance>::transfer {
id: asset_id.into(),
target: Runtime::Lookup::unlookup(to),
amount: value,
},
)?;
}
log3(
handle.context().address,
SELECTOR_LOG_TRANSFER,
handle.context().caller,
to,
solidity::encode_event_data(value),
)
.record(handle)?;
Ok(true)
}
#[precompile::public("transferFrom(address,address,uint256)")]
fn transfer_from(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
from: Address,
to: Address,
value: U256,
) -> EvmResult<bool> {
handle.record_log_costs_manual(3, 32)?;
let from: H160 = from.into();
let to: H160 = to.into();
let value = Self::u256_to_amount(value).in_field("value")?;
{
let caller: Runtime::AccountId =
Runtime::AddressMapping::into_account_id(handle.context().caller);
let from: Runtime::AccountId = Runtime::AddressMapping::into_account_id(from.clone());
let to: Runtime::AccountId = Runtime::AddressMapping::into_account_id(to);
if caller != from {
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(caller).into(),
pallet_assets::Call::<Runtime, Instance>::transfer_approved {
id: asset_id.into(),
owner: Runtime::Lookup::unlookup(from),
destination: Runtime::Lookup::unlookup(to),
amount: value,
},
)?;
} else {
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(from).into(),
pallet_assets::Call::<Runtime, Instance>::transfer {
id: asset_id.into(),
target: Runtime::Lookup::unlookup(to),
amount: value,
},
)?;
}
}
log3(
handle.context().address,
SELECTOR_LOG_TRANSFER,
from,
to,
solidity::encode_event_data(value),
)
.record(handle)?;
Ok(true)
}
#[precompile::public("name()")]
#[precompile::view]
fn name(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
) -> EvmResult<UnboundedBytes> {
handle.record_db_read::<Runtime>(
50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
)?;
let name = pallet_assets::Pallet::<Runtime, Instance>::name(asset_id)
.as_slice()
.into();
Ok(name)
}
#[precompile::public("symbol()")]
#[precompile::view]
fn symbol(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
) -> EvmResult<UnboundedBytes> {
handle.record_db_read::<Runtime>(
50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
)?;
let symbol = pallet_assets::Pallet::<Runtime, Instance>::symbol(asset_id)
.as_slice()
.into();
Ok(symbol)
}
#[precompile::public("decimals()")]
#[precompile::view]
fn decimals(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
) -> EvmResult<u8> {
handle.record_db_read::<Runtime>(
50 + (2 * <Runtime as pallet_assets::Config<Instance>>::StringLimit::get()) as usize,
)?;
Ok(pallet_assets::Pallet::<Runtime, Instance>::decimals(
asset_id,
))
}
#[precompile::public("minimumBalance()")]
#[precompile::view]
fn minimum_balance(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
) -> EvmResult<U256> {
handle.record_db_read::<Runtime>(207)?;
Ok(pallet_assets::Pallet::<Runtime, Instance>::minimum_balance(asset_id).into())
}
#[precompile::public("mint(address,uint256)")]
fn mint(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
to: Address,
value: U256,
) -> EvmResult<bool> {
handle.record_log_costs_manual(3, 32)?;
let to: H160 = to.into();
let value = Self::u256_to_amount(value).in_field("value")?;
{
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let to = Runtime::AddressMapping::into_account_id(to);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_assets::Call::<Runtime, Instance>::mint {
id: asset_id.into(),
beneficiary: Runtime::Lookup::unlookup(to),
amount: value,
},
)?;
}
log3(
handle.context().address,
SELECTOR_LOG_TRANSFER,
H160::default(),
to,
solidity::encode_event_data(value),
)
.record(handle)?;
Ok(true)
}
#[precompile::public("burn(address,uint256)")]
fn burn(
asset_id: AssetIdOf<Runtime, Instance>,
handle: &mut impl PrecompileHandle,
from: Address,
value: U256,
) -> EvmResult<bool> {
handle.record_log_costs_manual(3, 32)?;
let from: H160 = from.into();
let value = Self::u256_to_amount(value).in_field("value")?;
{
let origin = Runtime::AddressMapping::into_account_id(handle.context().caller);
let from = Runtime::AddressMapping::into_account_id(from);
RuntimeHelper::<Runtime>::try_dispatch(
handle,
Some(origin).into(),
pallet_assets::Call::<Runtime, Instance>::burn {
id: asset_id.into(),
who: Runtime::Lookup::unlookup(from),
amount: value,
},
)?;
}
log3(
handle.context().address,
SELECTOR_LOG_TRANSFER,
from,
H160::default(),
solidity::encode_event_data(value),
)
.record(handle)?;
Ok(true)
}
fn u256_to_amount(value: U256) -> MayRevert<BalanceOf<Runtime, Instance>> {
value
.try_into()
.map_err(|_| RevertReason::value_is_too_large("balance type").into())
}
}