Search code examples
rustsubstratepolkadot

Why use <T::Lookup as StaticLookup>::Source instead of plain T::AccountId?


According to this PR, all dispatchable calls should use <T::Lookup as StaticLookup>::Source instead of T::AccountId. Why is it better to handle transfers with the lookups instead of addressing users with their accounts? How the lookups in Substrate works, is there an alternative to the StaticLookup?

And lastly, are there any other types than the IdentityLookup and how would you use them?

type Lookup = IdentityLookup<AccountId>;

Solution

  • StaticLookup is an abstraction over an address which can convert multiple different address types into an underlying AccountId.

    Imagine an extrinsic which uses only AccountId. The only way to interact with that function would be to provide the raw AccountId for the account on chain.

    Instead, with StaticLookup, you are able to provide any compatible address format, and there will be additional logic that is used to then convert that address into an underlying account.

    Imagine the following:

    enum Address {
        AccountId([u8; 32]),  // 32 byte account id
        String(Vec<u8>),      // arbitrary string
    }
    

    This is a practical example of how you would allow people to use a name service on your chain. For example you can do:

    transfer(b"shawntabrizi", 100 UNIT)
    

    In addition to:

    transfer(5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY, 100 UNIT)
    

    StaticLookup will contain any appropriate code to convert from the known formats into the final AccountId you need.

    Imagine the following code:

    struct NameServiceLookup;
    impl StaticLookup for NameServiceLookup {
        type Source = Address;
        type Target = AccountId;
    
        fn lookup(a: Address) -> Result<AccountId, LookupError> {
            match a {
                Address::AccountId(id) => Ok(id),
                Address::String(string) => {
                    string_to_account_id(string).ok_or(LookupError)
                },
            }
        }
    
        fn unlookup(a: [u8; 32]) -> Address {
            Address::AccountId(a)
        }
    }
    

    Here you can see we have special logic that would handle the address if it was either an AccountId or a String. This is ultimately what StaticLookup would provide.

    The implementation of IdentityLookup is a simple passthrough, which returns exactly the input for the output. So this would be what you want to use when you do not have any such fancy logic, and want to make all StaticLookup exactly the same as AccountId directly:

    pub struct IdentityLookup<T>(PhantomData<T>);
    impl<T: Codec + Clone + PartialEq + Debug> 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 }
    }