Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -636,4 +636,32 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
fn migration_v2_foreign_asset_set_reserve_weight() -> Weight {
// Proof Size summary in bytes:
// Measured: `256`
// Estimated: `0`
// Minimum execution time: 9_596_000 picoseconds.
Weight::from_parts(10_031_000, 0)
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -632,4 +632,32 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
fn migration_v2_foreign_asset_set_reserve_weight() -> Weight {
// Proof Size summary in bytes:
// Measured: `256`
// Estimated: `0`
// Minimum execution time: 9_596_000 picoseconds.
Weight::from_parts(10_031_000, 0)
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,32 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
fn migration_v2_foreign_asset_set_reserve_weight() -> Weight {
// Proof Size summary in bytes:
// Measured: `256`
// Estimated: `0`
// Minimum execution time: 9_596_000 picoseconds.
Weight::from_parts(10_031_000, 0)
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -656,4 +656,25 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -656,4 +656,25 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -648,4 +648,25 @@ impl<T: frame_system::Config> pallet_assets::WeightInfo for WeightInfo<T> {
.saturating_add(T::DbWeight::get().reads(1))
.saturating_add(T::DbWeight::get().writes(1))
}
fn get_name() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_390_000 picoseconds.
Weight::from_parts(10_914_000, 0)
}
fn get_symbol() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_587_000 picoseconds.
Weight::from_parts(11_121_000, 0)
}
fn get_decimals() -> Weight {
// Proof Size summary in bytes:
// Measured: `344`
// Estimated: `0`
// Minimum execution time: 10_591_000 picoseconds.
Weight::from_parts(11_015_000, 0)
}
}
27 changes: 27 additions & 0 deletions prdoc/pr_10971.prdoc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

need to be renamed to 10971.prdoc

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
title: "Implement IERC20Metadata for pallet-assets precompiles"

doc:
- audience: Runtime Dev
description: |
Implements the missing ERC20 metadata functions (`name`, `symbol`, `decimals`) for the
pallet-assets precompile to provide full ERC20 compatibility. These functions are essential
for proper EVM wallet and tooling integration.

The precompile implementation reads metadata from pallet-assets storage and returns properly
formatted values with appropriate gas charging using dedicated weight functions. All functions
include proper error handling for missing metadata and invalid UTF-8 encoding.

Benchmarks have been added to measure the weight of metadata reads, and corresponding weight
functions have been implemented in the WeightInfo trait.

The IERC20.sol interface file has been reorganized to clearly separate and document methods
from the base IERC20 interface and the IERC20Metadata extension, with links to the original
OpenZeppelin contracts for better maintainability.

crates:
- name: ethereum-standards
bump: minor
- name: pallet-assets-precompiles
bump: minor
- name: pallet-assets
bump: minor
48 changes: 48 additions & 0 deletions substrate/frame/assets/precompiles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ where
IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
IERC20Calls::transferFrom(call) => Self::transfer_from(asset_id, call, env),
IERC20Calls::name(_) => Self::name(asset_id, env),
IERC20Calls::symbol(_) => Self::symbol(asset_id, env),
IERC20Calls::decimals(_) => Self::decimals(asset_id, env),
}
}
}
Expand Down Expand Up @@ -322,4 +325,49 @@ where

return Ok(IERC20::transferFromCall::abi_encode_returns(&true));
}

/// Execute the name call.
fn name(
asset_id: <Runtime as Config<Instance>>::AssetId,
env: &mut impl Ext<T = Runtime>,
) -> Result<Vec<u8>, Error> {
env.charge(<Runtime as Config<Instance>>::WeightInfo::get_name())?;

let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
.ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;

let name = alloc::string::String::from_utf8(metadata.name.to_vec())
.map_err(|_| Error::Revert(Revert { reason: "Invalid UTF-8 in name".into() }))?;

Ok(IERC20::nameCall::abi_encode_returns(&name))
}

/// Execute the symbol call.
fn symbol(
asset_id: <Runtime as Config<Instance>>::AssetId,
env: &mut impl Ext<T = Runtime>,
) -> Result<Vec<u8>, Error> {
env.charge(<Runtime as Config<Instance>>::WeightInfo::get_symbol())?;

let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
.ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;

let symbol = alloc::string::String::from_utf8(metadata.symbol.to_vec())
.map_err(|_| Error::Revert(Revert { reason: "Invalid UTF-8 in symbol".into() }))?;

Ok(IERC20::symbolCall::abi_encode_returns(&symbol))
}

/// Execute the decimals call.
fn decimals(
asset_id: <Runtime as Config<Instance>>::AssetId,
env: &mut impl Ext<T = Runtime>,
) -> Result<Vec<u8>, Error> {
env.charge(<Runtime as Config<Instance>>::WeightInfo::get_decimals())?;

let metadata = pallet_assets::Pallet::<Runtime, Instance>::get_metadata(asset_id)
.ok_or(Error::Revert(Revert { reason: "Metadata not found".into() }))?;

Ok(IERC20::decimalsCall::abi_encode_returns(&metadata.decimals))
}
}
45 changes: 45 additions & 0 deletions substrate/frame/assets/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,5 +641,50 @@ benchmarks_instance_pallet! {
assert_eq!(Reserves::<T, I>::get(id)[0], reserve);
}

get_name {
Copy link
Copy Markdown
Contributor

@pgherveou pgherveou Feb 9, 2026

Choose a reason for hiding this comment

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

these are identical we should probably only have one and just call it for both methods in the precompile
@0xRVE can you do that in a follow up, so we can merge this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

will do

let (asset_id, caller, _) = create_default_asset::<T, I>(true);
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let name_bytes = vec![0u8; T::StringLimit::get() as usize];
let symbol_bytes = vec![0u8; T::StringLimit::get() as usize];
let origin = SystemOrigin::Signed(caller).into();
Assets::<T, I>::set_metadata(origin, asset_id.clone(), name_bytes.clone(), symbol_bytes, 12)?;
}: {
let _ = Pallet::<T, I>::get_metadata(asset_id.clone().into());
} verify {
let metadata = Pallet::<T, I>::get_metadata(asset_id.into());
assert!(metadata.is_some());
assert_eq!(metadata.unwrap().name.to_vec(), name_bytes);
}

get_symbol {
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let name_bytes = vec![0u8; T::StringLimit::get() as usize];
let symbol_bytes = vec![0u8; T::StringLimit::get() as usize];
let origin = SystemOrigin::Signed(caller).into();
Assets::<T, I>::set_metadata(origin, asset_id.clone(), name_bytes, symbol_bytes.clone(), 12)?;
}: {
let _ = Pallet::<T, I>::get_metadata(asset_id.clone().into());
} verify {
let metadata = Pallet::<T, I>::get_metadata(asset_id.into());
assert!(metadata.is_some());
assert_eq!(metadata.unwrap().symbol.to_vec(), symbol_bytes);
}

get_decimals {
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
let name_bytes = vec![0u8; T::StringLimit::get() as usize];
let symbol_bytes = vec![0u8; T::StringLimit::get() as usize];
let origin = SystemOrigin::Signed(caller).into();
Assets::<T, I>::set_metadata(origin, asset_id.clone(), name_bytes, symbol_bytes, 12)?;
}: {
let _ = Pallet::<T, I>::get_metadata(asset_id.clone().into());
} verify {
let metadata = Pallet::<T, I>::get_metadata(asset_id.into());
assert!(metadata.is_some());
assert_eq!(metadata.unwrap().decimals, 12);
}

impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test)
}
Loading
Loading