4

I noticed https://github.com/paritytech/substrate/issues/6042 was recently resolved, replacing T::AccountId with <T::Lookup as StaticLookup>::Source. Substrate docs don't mention what is the benefit of one over the other.

It seems related to pallet-assets and might be necessary in more complex scenarios, but maybe someone can elaborate on it?

2 Answers 2

8

Shawn has explained a lot.

Let me give you a real example.

I've implemented a custom AccoundIdLookup for Darwinia, which allows transfer from Substrate address to EVM address directly.

https://github.com/darwinia-network/darwinia-common/blob/main/node/runtime/pangolin/src/pallets/system.rs#L42 https://github.com/darwinia-network/darwinia-common/blob/main/node/primitives/src/lib.rs#L181-L201

Before this, the EVM user needs to calculate their Substrate address based on the EVM address. Then transfer some balance to the Substrate account to fund their EVM account.

But now, they just need to transfer from an H256 address to an H160 address directly. Because the pallet-balances is accepting a lookup type as the dest. It's more flexible than the AccountId.

5

You can start by looking at the trait:

/// Means of changing one type into another in a manner dependent on the source type.
/// This variant is different to `Lookup` in that it doesn't (can cannot) require any
/// context.
pub trait StaticLookup {
    /// Type to lookup from.
    type Source: Codec + Clone + PartialEq + Debug + TypeInfo;
    /// Type to lookup into.
    type Target;
    /// Attempt a lookup.
    fn lookup(s: Self::Source) -> Result<Self::Target, LookupError>;
    /// Convert from Target back to Source.
    fn unlookup(t: Self::Target) -> Self::Source;
}

Static lookup just describes some logic which converts from one type to another. In the most basic implementation, you can simply create the "identity lookup" conversion, taking an existing type and mapping it to itself:

/// A lookup implementation returning the input value.
#[derive(Default)]
pub struct IdentityLookup<T>(PhantomData<T>);
impl<T: Codec + Clone + PartialEq + Debug + TypeInfo> StaticLookup for IdentityLookup<T> {
    type Source = T;
    type Target = T;
    fn lookup(x: T) -> Result<T, LookupError> {
        Ok(x)
    }
    fn unlookup(x: T) -> T {
        x
    }
}

However, with this abstraction, you can also program more complex lookup patterns, for example converting domain name (like shawntabrizi.dot) or index to the appropriate account id.

From Pallet Indices:

impl<T: Config> StaticLookup for Pallet<T> {
    type Source = MultiAddress<T::AccountId, T::AccountIndex>;
    type Target = T::AccountId;

    fn lookup(a: Self::Source) -> Result<Self::Target, LookupError> {
        Self::lookup_address(a).ok_or(LookupError)
    }

    fn unlookup(a: Self::Target) -> Self::Source {
        MultiAddress::Id(a)
    }
}

This abstraction allows substrate developers to be more flexible on the input users give to pallets when referencing an account, and allow the underlying pallet logic to handle the conversion to an appropriate account id.

2
  • So public APIs (extrinsics) should prefer lookups, but internally T::AccountId is fine or what is the general rule to apply here?
    – nazar-pc
    Aug 18 at 13:15
  • When a user should provide some T::AccountId, you can instead ask them for <T::Lookup as StaticLookup>::Source, which allows the caller to be more flexible in what data is sent to the chain. In the end, you will need to do let who = T::Lookup::lookup(who)?; in your code to find the underlying account.
    – Shawn Tabrizi
    Aug 18 at 13:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.