Substrate Pallet Code Analysis: Sudo
Tags: substrate, rust, algorithms, blockchain
Published: 2022-07-28 05:12:00



## Overview

In substrate-based chain, there is a _god-like_ account who can call a privileged function which is called **_sudo_** account. Sudo pallet composed of that function what sudo account can do. There should be only one sudo account for the Runtime, which is called 'sudo-key'. Before launching public blockchain network, having sudo account could be useful but after launching, most of the _Parachain_ projects tend to get rid of this Sudo pallet for safety. 

_Let's look at how this pallet is implemented._

---

## Source Code Analysis

### Storage

There is only one storage for this pallet. Storage type is _StorageValue_ to store only one sudo key account for Runtime.

```
#[pallet::storage]
#[pallet::getter(fn key)]
pub(super) type Key<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
```

### Dispatch Call

**sudo**
> _Dispatch a call with Root origin._

> _Params_
>> - origin: _who_ calls this function
>> - call: Some Call type like dispatch calls. When building runtime, Compiler collects all the **Call** type into one single Call Enum type by _#[pallet::call] macro_. Code below is how Substrate deals with Call data type.

In Runtime,

```
pub enum Call {
 
    System( <Pallet<Runtime> as Callable<Runtime>>::Call ),
    Balances( <Pallet<Runtime> as Callable<Runtime>>::Call ),
    Staking( <Pallet<Runtime> as Callable<Runtime>>::Call ),
    ...
}

```

For example, in Staking Pallet, any dispatch call is defined in **Call** enum type 

``` 
pub enum Call {
   ...
   Staking(pallet_staking::Call::<Runtime>)
   ...
}

pub enum Call {
    bond { controller, value, payee },
    nominate { targets }
    ....
}
```

**Analysis**

1. Check whether origin is signed.
2. Check whether caller is 'sudo-key account'
3. dispatch_bypass_filter: Skip checking origin filter in dispatch call.
4. Emit 'Sudid' event
5. Sudo account doesn't pay fee

```
pub fn sudo(
    origin: OriginFor<T>,
    call: Box<<T as Config>::Call>,
) -> DispatchResultWithPostInfo {
    
    // 1
    let sender = ensure_signed(origin)?;
    
    // 2
    ensure!(Self::key().map_or(false, |k| sender == k), Error::<T>::RequireSudo);
    
    // 3 
    let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());

    // 4
    Self::deposit_event(Event::Sudid { sudo_result: res.map(|_| ()).map_err(|e| e.error) });
    
    // 5 
    Ok(Pays::No.into())
}
```

**sudo_unchecked_weight**

> _The logic of this method is same with _sudo_ but this time, **Sudo** account can control the **Weight** of the call. As we can see the parameter we give as **_weight** is used in the weight calculation in _#[pallet::weight((*_weight, call.get_dispatch_info().class))]_ macro._

> _Params_ 
>> - _weight: Amount of weight sudo want to control

```
#[pallet::weight((*_weight, call.get_dispatch_info().class))]
pub fn sudo_unchecked_weight(
    origin: OriginFor<T>,
    call: Box<<T as Config>::Call>,
    _weight: Weight,
) -> DispatchResultWithPostInfo {
    
    // Same logic with sudo
}
```

**set_key**

> _Set sudo key with different account_

> Params
>> - new: New sudo key account. Type is Source type of StaticLookup trait. This type is for converting any source type into AccountId. Let's take a closer look!

First of all, _StaticLookup_ is defined as

1. type Source: any type that we want to look
2. type Target: Type that we want to covert
3. fn lookup: Convert Source type into Target type
4. fn unlookup: Convert Target type into Source type

```
pub trait StaticLookup {
    // 1
    pub type Source;
    // 2 
    pub type Target;
    // 3 
    fn lookup(s: Self::Source) -> Result<Self::Target, LookupError>
    // 4
    fn unlookup(t: Self::Target) -> Self::Source;
}
```

In _frame system pallet_, Target is defined with AccountId type, which means any Source type that we want to look can be converted into _AccountId_ type when we call _lookup_ method.

```
pub trait Config {
    ...

    type lookup: StaticLookup<Target = AccountId>

    ...
}
```

In Runtime, when we look at how frame_system's config is implemented for Runtime, _pallet_indices_ is defined as lookup, which means _pallet_indices_ must implement StaticLookup trait.

```
impl frame_system::Config for Runtime {
  
    ...

    type lookup = Indices(pallet_indices)

    ...
}
```

Inside pallet_indices
1. impl StaticLookup for Pallet
2. MultiAddress: Type of address formats
3. fn lookup_address(): Returns address if exists depending on the formats of address. Here, type of address format we are looking for is _Account Id_ and _AccountIndex_.
```
// 1
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)
    } 
}

// 2
pub enum MultiAddress<AccountId, AccountIndex> {
    Id(AccountId), // public address
    Index(AccountIndex), // Account index for database
    ...
}

// 3
impl<T: Config> Pallet<T> {

    fn lookup_index(index: T::AccountIndex) -> Option<T::AccountId> {
        // where Accounts is a storage that maps _AccountIndex_ to (AccountId, Balance)
        Accounts::<T>::get(index).map(|x| x.0)

    }  
    
    fn lookup_address(a: Multiaddress<T::AccountId, T::AccountIndex> -> Option<T::AccountId> {
         match a {
              Multiaddress::Id(account) => Some(account)
              Multiaddress::Index(index) => Self::lookup_index(index)
         }
    }
}

#[pallet::storage]
pub type Accounts<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountIndex, (T::AccountId, BalanceOf<T>, bool)>;
```

**Analysis**

1. Check whether origin is signed.
2. Check whether caller is 'sudo-key account'
3. Convert _MultiAddress_ type into AccountId,
4. Emit 'KeyChanged' event 
5. Put new sudo-key into Key storage 
6. Sudo doesn't pay a fee

```
#[pallet::weight(0)]
pub fn set_key(
    origin: OriginFor<T>,
    new: <T::Lookup as StaticLookup>::Source,
) -> DispatchResultWithPostInfo {
   
    // 1
    let sender = ensure_signed(origin)?;

    // 2
    ensure!(Self::key().map_or(false, |k| sender == k), Error::<T>::RequireSudo);
    
    // 3
    let new = T::Lookup::lookup(new)?;
    
    // 4
    Self::deposit_event(Event::KeyChanged { old_sudoer: Key::<T>::get() });
    
    // 5
    Key::<T>::put(&new);
    
    // 6
    Ok(Pays::No.into())
}
```

**sudo_as**

> _Sudo account calls and dispatches a call with a signed account._
> _Make signed account as sudo account which pays no fee_
> _Params_
>> - who: The one who calls the dispatch call

```
pub fn sudo_as(
    origin: OriginFor<T>,
    who: <T::Lookup as StaticLookup>::Source,
    call: Box<<T as Config>::Call>,
) -> DispatchResultWithPostInfo {

    let sender = ensure_signed(origin)?;
    ensure!(Self::key().map_or(false, |k| sender == k), Error::<T>::RequireSudo);

    let who = T::Lookup::lookup(who)?;

    let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Signed(who).into());
	
    Self::deposit_event(Event::SudoAsDone {
				sudo_result: res.map(|_| ()).map_err(|e| e.error),
			});
			
    Ok(Pays::No.into())
}
```

## Conclusion

Sudo is special feature that only exists in Substrate based chain which can do everything and it is not preferred to have in **public** blockchain. 

_Feel free to leave a comment if you have some questions._

---

## Reference

[Sudo](https://github.com/paritytech/substrate/tree/master/frame/sudo)
[Frame System](https://github.com/paritytech/substrate/tree/master/frame/system)
[Indices]
(https://github.com/paritytech/substrate/tree/master/frame/indices)