code/precompiles/src/proxy.rs

proxy.rs

552 lines · 20,806 bytes · 71136ad109RawGitHub
use core::marker::PhantomData;

use crate::{PrecompileExt, PrecompileHandleExt};

use alloc::format;
use fp_evm::{ExitError, PrecompileFailure};
use frame_support::dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo};
use frame_support::traits::IsSubType;
use frame_system::RawOrigin;
use pallet_evm::{AddressMapping, PrecompileHandle};
use pallet_subtensor_proxy as pallet_proxy;
use precompile_utils::EvmResult;
use sp_core::{H256, U256};
use sp_runtime::{
    DispatchError,
    codec::DecodeLimit,
    traits::{
        AsSystemOriginSigner, Dispatchable, SaturatedConversion, StaticLookup, UniqueSaturatedInto,
    },
};
use sp_std::boxed::Box;
use sp_std::convert::{TryFrom, TryInto};
use sp_std::vec;
use sp_std::vec::Vec;
use subtensor_runtime_common::ProxyType;
pub struct ProxyPrecompile<R>(PhantomData<R>);
const MAX_DECODE_DEPTH: u32 = 8;

impl<R> PrecompileExt<R::AccountId> for ProxyPrecompile<R>
where
    R: frame_system::Config
        + pallet_balances::Config
        + pallet_evm::Config
        + pallet_subtensor::Config
        + pallet_proxy::Config<ProxyType = ProxyType>
        + pallet_shield::Config
        + pallet_subtensor_proxy::Config
        + Send
        + Sync
        + scale_info::TypeInfo,
    R::AccountId: From<[u8; 32]> + Into<[u8; 32]>,
    <R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
    <R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
    <R as frame_system::Config>::RuntimeCall: From<pallet_subtensor::Call<R>>
        + From<pallet_proxy::Call<R>>
        + GetDispatchInfo
        + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
        + IsSubType<pallet_balances::Call<R>>
        + IsSubType<pallet_subtensor::Call<R>>
        + IsSubType<pallet_shield::Call<R>>
        + IsSubType<pallet_subtensor_proxy::Call<R>>,
    <R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
    <<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
{
    const INDEX: u64 = 2059;
}

#[precompile_utils::precompile]
impl<R> ProxyPrecompile<R>
where
    R: frame_system::Config
        + pallet_balances::Config
        + pallet_evm::Config
        + pallet_subtensor::Config
        + pallet_proxy::Config<ProxyType = ProxyType>
        + pallet_shield::Config
        + pallet_subtensor_proxy::Config
        + Send
        + Sync
        + scale_info::TypeInfo,
    R::AccountId: From<[u8; 32]> + Into<[u8; 32]>,
    <R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
    <R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
    <R as frame_system::Config>::RuntimeCall: From<pallet_subtensor::Call<R>>
        + From<pallet_proxy::Call<R>>
        + GetDispatchInfo
        + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>
        + IsSubType<pallet_balances::Call<R>>
        + IsSubType<pallet_subtensor::Call<R>>
        + IsSubType<pallet_shield::Call<R>>
        + IsSubType<pallet_subtensor_proxy::Call<R>>,
    <<R as frame_system::Config>::Lookup as StaticLookup>::Source: From<R::AccountId>,
{
    #[precompile::public("createPureProxy(uint8,uint32,uint16)")]
    pub fn create_pure_proxy(
        handle: &mut impl PrecompileHandle,
        proxy_type_: u8,
        delay: u32,
        index: u16,
    ) -> EvmResult<H256> {
        let account_id = handle.caller_account_id::<R>();
        let proxy_type =
            ProxyType::try_from(proxy_type_).map_err(|_| PrecompileFailure::Error {
                exit_status: ExitError::Other("Invalid proxy type".into()),
            })?;

        let call = pallet_proxy::Call::<R>::create_pure {
            proxy_type,
            delay: delay.into(),
            index,
        };

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id.clone()))?;

        // Success!
        // Try to get proxy address
        let proxy_address: [u8; 32] =
            pallet_proxy::pallet::Pallet::<R>::pure_account(&account_id, &proxy_type, index, None)
                .map_err(|_| PrecompileFailure::Error {
                    exit_status: ExitError::Other("Proxy not found".into()),
                })?
                .into();

        // Check if in the proxies map
        let proxy_entry = pallet_proxy::pallet::Pallet::<R>::proxies(proxy_address.into());
        if proxy_entry
            .0
            .iter()
            .any(|p| account_id == p.delegate && proxy_type == p.proxy_type)
        {
            return Ok(proxy_address.into());
        }

        Err(PrecompileFailure::Error {
            exit_status: ExitError::Other("Proxy not found".into()),
        })
    }

    #[precompile::public("killPureProxy(bytes32,uint8,uint16,uint32,uint32)")]
    pub fn kill_pure_proxy(
        handle: &mut impl PrecompileHandle,
        spawner: H256,
        proxy_type: u8,
        index: u16,
        height: u32,
        ext_index: u32,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error {
            exit_status: ExitError::Other("Invalid proxy type".into()),
        })?;

        let call = pallet_proxy::Call::<R>::kill_pure {
            spawner: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                spawner.0.into(),
            ),
            proxy_type,
            index,
            height: height.into(),
            ext_index: ext_index.into(),
        };

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("proxyCall(bytes32,uint8[],uint8[])")]
    pub fn proxy_call(
        handle: &mut impl PrecompileHandle,
        real: H256,
        force_proxy_type: Vec<u8>,
        call: Vec<u8>,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();

        let call = <R as pallet_proxy::Config>::RuntimeCall::decode_with_depth_limit(
            MAX_DECODE_DEPTH,
            &mut &call[..],
        )
        .map_err(|_| PrecompileFailure::Error {
            exit_status: ExitError::Other("The raw call data not correctly encoded".into()),
        })?;

        let mut proxy_type: Option<ProxyType> = None;
        if let Some(p) = force_proxy_type.first() {
            let proxy_type_ = ProxyType::try_from(*p).map_err(|_| PrecompileFailure::Error {
                exit_status: ExitError::Other("Invalid proxy type".into()),
            })?;
            proxy_type = Some(proxy_type_);
        };

        let call = pallet_proxy::Call::<R>::proxy {
            real: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                real.0.into(),
            ),
            force_proxy_type: proxy_type,
            call: Box::new(call),
        };

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))?;

        let real_account_id = R::AccountId::from(real.0.into());

        let last_call_result = pallet_proxy::LastCallResult::<R>::get(real_account_id);
        match last_call_result {
            Some(last_call_result) => match last_call_result {
                Ok(()) => Ok(()),
                Err(e) => Err(PrecompileFailure::Error {
                    exit_status: ExitError::Other(format!("{e:?}").into()),
                }),
            },
            None => Err(PrecompileFailure::Error {
                exit_status: ExitError::Other("Proxy execution failed".into()),
            }),
        }
    }

    #[precompile::public("addProxy(bytes32,uint8,uint32)")]
    pub fn add_proxy(
        handle: &mut impl PrecompileHandle,
        delegate: H256,
        proxy_type: u8,
        delay: u32,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error {
            exit_status: ExitError::Other("Invalid proxy type".into()),
        })?;

        let call = pallet_proxy::Call::<R>::add_proxy {
            delegate: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                delegate.0.into(),
            ),
            proxy_type,
            delay: delay.into(),
        };

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("removeProxy(bytes32,uint8,uint32)")]
    pub fn remove_proxy(
        handle: &mut impl PrecompileHandle,
        delegate: H256,
        proxy_type: u8,
        delay: u32,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let proxy_type = ProxyType::try_from(proxy_type).map_err(|_| PrecompileFailure::Error {
            exit_status: ExitError::Other("Invalid proxy type".into()),
        })?;

        let call = pallet_proxy::Call::<R>::remove_proxy {
            delegate: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                delegate.0.into(),
            ),
            proxy_type,
            delay: delay.into(),
        };

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("removeProxies()")]
    pub fn remove_proxies(handle: &mut impl PrecompileHandle) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();

        let call = pallet_proxy::Call::<R>::remove_proxies {};

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("pokeDeposit()")]
    pub fn poke_deposit(handle: &mut impl PrecompileHandle) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();

        let call = pallet_proxy::Call::<R>::poke_deposit {};

        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("getProxies(bytes32)")]
    #[precompile::view]
    pub fn get_proxies(
        handle: &mut impl PrecompileHandle,
        account_id: H256,
    ) -> EvmResult<Vec<(H256, U256, U256)>> {
        handle.record_db_reads::<R>(1)?;
        let account_id = R::AccountId::from(account_id.0.into());

        let proxies = pallet_proxy::pallet::Pallet::<R>::proxies(account_id);
        let mut result: Vec<(H256, U256, U256)> = vec![];
        for proxy in proxies.0 {
            let delegate: [u8; 32] = proxy.delegate.into();
            let proxy_type: u8 = proxy.proxy_type.into();
            let delay: u32 = proxy
                .delay
                .try_into()
                .map_err(|_| PrecompileFailure::Error {
                    exit_status: ExitError::Other("Invalid delay".into()),
                })?;

            result.push((delegate.into(), proxy_type.into(), delay.into()));
        }

        Ok(result)
    }

    #[precompile::public("getProxyDeposit(bytes32)")]
    #[precompile::view]
    pub fn get_proxy_deposit(
        handle: &mut impl PrecompileHandle,
        account_id: H256,
    ) -> EvmResult<U256> {
        handle.record_db_reads::<R>(1)?;
        let (_, deposit) = pallet_proxy::Proxies::<R>::get(R::AccountId::from(account_id.0));
        Ok(U256::from(deposit.saturated_into::<u128>()))
    }

    #[precompile::public("getAnnouncements(bytes32)")]
    #[precompile::view]
    pub fn get_announcements(
        handle: &mut impl PrecompileHandle,
        account_id: H256,
    ) -> EvmResult<(Vec<(H256, H256, u64)>, U256)> {
        handle.record_db_reads::<R>(1)?;
        let (announcements, deposit) =
            pallet_proxy::Announcements::<R>::get(R::AccountId::from(account_id.0));
        let announcements = announcements
            .into_iter()
            .map(|announcement| {
                (
                    H256::from(<R::AccountId as Into<[u8; 32]>>::into(
                        announcement.real().clone(),
                    )),
                    H256::from_slice(announcement.call_hash().as_ref()),
                    (*announcement.height()).unique_saturated_into(),
                )
            })
            .collect();
        Ok((announcements, U256::from(deposit.saturated_into::<u128>())))
    }

    #[precompile::public("getLastCallResult(bytes32)")]
    #[precompile::view]
    pub fn get_last_call_result(
        handle: &mut impl PrecompileHandle,
        account_id: H256,
    ) -> EvmResult<(bool, bool, u8, u8, H256)> {
        handle.record_db_reads::<R>(1)?;
        let Some(result) = pallet_proxy::LastCallResult::<R>::get(R::AccountId::from(account_id.0))
        else {
            return Ok((false, false, 0, 0, H256::zero()));
        };
        match result {
            Ok(()) => Ok((true, true, 0, 0, H256::zero())),
            Err(error) => {
                let (kind, pallet_index, error_data) = dispatch_error_metadata(error);
                Ok((true, false, kind, pallet_index, error_data))
            }
        }
    }

    #[precompile::public("isRealPaysFee(bytes32,bytes32)")]
    #[precompile::view]
    pub fn is_real_pays_fee(
        handle: &mut impl PrecompileHandle,
        real: H256,
        delegate: H256,
    ) -> EvmResult<bool> {
        handle.record_db_reads::<R>(1)?;
        Ok(pallet_proxy::RealPaysFee::<R>::contains_key(
            R::AccountId::from(real.0),
            R::AccountId::from(delegate.0),
        ))
    }

    #[precompile::public("announce(bytes32,bytes32)")]
    pub fn announce(
        handle: &mut impl PrecompileHandle,
        real: H256,
        call_hash: H256,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call_hash = DecodeLimit::decode_all_with_depth_limit(1, &mut &call_hash.as_bytes()[..])
            .map_err(|_| PrecompileFailure::Error {
                exit_status: ExitError::Other(
                    "runtime call hash is not compatible with bytes32".into(),
                ),
            })?;
        let call = pallet_proxy::Call::<R>::announce {
            real: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                real.0.into(),
            ),
            call_hash,
        };
        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("removeAnnouncement(bytes32,bytes32)")]
    pub fn remove_announcement(
        handle: &mut impl PrecompileHandle,
        real: H256,
        call_hash: H256,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call_hash = DecodeLimit::decode_all_with_depth_limit(1, &mut &call_hash.as_bytes()[..])
            .map_err(|_| PrecompileFailure::Error {
                exit_status: ExitError::Other(
                    "runtime call hash is not compatible with bytes32".into(),
                ),
            })?;
        let call = pallet_proxy::Call::<R>::remove_announcement {
            real: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                real.0.into(),
            ),
            call_hash,
        };
        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("rejectAnnouncement(bytes32,bytes32)")]
    pub fn reject_announcement(
        handle: &mut impl PrecompileHandle,
        delegate: H256,
        call_hash: H256,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call_hash = DecodeLimit::decode_all_with_depth_limit(1, &mut &call_hash.as_bytes()[..])
            .map_err(|_| PrecompileFailure::Error {
                exit_status: ExitError::Other(
                    "runtime call hash is not compatible with bytes32".into(),
                ),
            })?;
        let call = pallet_proxy::Call::<R>::reject_announcement {
            delegate: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                delegate.0.into(),
            ),
            call_hash,
        };
        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }

    #[precompile::public("setRealPaysFee(bytes32,bool)")]
    pub fn set_real_pays_fee(
        handle: &mut impl PrecompileHandle,
        delegate: H256,
        pays_fee: bool,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_proxy::Call::<R>::set_real_pays_fee {
            delegate: <<R as frame_system::Config>::Lookup as StaticLookup>::Source::from(
                delegate.0.into(),
            ),
            pays_fee,
        };
        handle.try_dispatch_runtime_call::<R, _>(call, RawOrigin::Signed(account_id))
    }
}

fn dispatch_error_metadata(error: DispatchError) -> (u8, u8, H256) {
    let mut data = [0u8; 32];
    match error {
        DispatchError::Other(_) => (1, 0, H256::zero()),
        DispatchError::CannotLookup => (2, 0, H256::zero()),
        DispatchError::BadOrigin => (3, 0, H256::zero()),
        DispatchError::Module(module) => {
            data[..4].copy_from_slice(&module.error);
            (4, module.index, H256::from(data))
        }
        DispatchError::ConsumerRemaining => (5, 0, H256::zero()),
        DispatchError::NoProviders => (6, 0, H256::zero()),
        DispatchError::TooManyConsumers => (7, 0, H256::zero()),
        DispatchError::Token(_) => (8, 0, H256::zero()),
        DispatchError::Arithmetic(_) => (9, 0, H256::zero()),
        DispatchError::Transactional(_) => (10, 0, H256::zero()),
        DispatchError::Exhausted => (11, 0, H256::zero()),
        DispatchError::Corruption => (12, 0, H256::zero()),
        DispatchError::Unavailable => (13, 0, H256::zero()),
        DispatchError::RootNotAllowed => (14, 0, H256::zero()),
        DispatchError::Trie(_) => (15, 0, H256::zero()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::PrecompileExt;
    use crate::mock::{
        AccountId, Runtime, addr_from_index, new_test_ext, precompiles, selector_u32,
    };
    use precompile_utils::solidity::encode_with_selector;
    use precompile_utils::testing::PrecompileTesterExt;

    #[test]
    fn proxy_state_views_return_typed_values_and_missing_state() {
        new_test_ext().execute_with(|| {
            let caller = addr_from_index(0x80b1);
            let address = addr_from_index(ProxyPrecompile::<Runtime>::INDEX);
            let real = AccountId::from([0x31; 32]);
            let delegate = AccountId::from([0x32; 32]);
            let real_word = H256::from_slice(real.as_ref());
            let delegate_word = H256::from_slice(delegate.as_ref());
            let precompiles = precompiles::<ProxyPrecompile<Runtime>>();

            precompiles
                .prepare_test(
                    caller,
                    address,
                    encode_with_selector(selector_u32("getProxyDeposit(bytes32)"), (real_word,)),
                )
                .with_static_call(true)
                .execute_returns(U256::zero());

            precompiles
                .prepare_test(
                    caller,
                    address,
                    encode_with_selector(
                        selector_u32("getAnnouncements(bytes32)"),
                        (delegate_word,),
                    ),
                )
                .with_static_call(true)
                .execute_returns((Vec::<(H256, H256, u64)>::new(), U256::zero()));

            precompiles
                .prepare_test(
                    caller,
                    address,
                    encode_with_selector(selector_u32("getLastCallResult(bytes32)"), (real_word,)),
                )
                .with_static_call(true)
                .execute_returns((false, false, 0_u8, 0_u8, H256::zero()));

            pallet_proxy::LastCallResult::<Runtime>::insert(
                &real,
                Err::<(), _>(DispatchError::BadOrigin),
            );
            precompiles
                .prepare_test(
                    caller,
                    address,
                    encode_with_selector(selector_u32("getLastCallResult(bytes32)"), (real_word,)),
                )
                .with_static_call(true)
                .execute_returns((true, false, 3_u8, 0_u8, H256::zero()));

            pallet_proxy::RealPaysFee::<Runtime>::insert(&real, &delegate, ());
            precompiles
                .prepare_test(
                    caller,
                    address,
                    encode_with_selector(
                        selector_u32("isRealPaysFee(bytes32,bytes32)"),
                        (real_word, delegate_word),
                    ),
                )
                .with_static_call(true)
                .execute_returns(true);
        });
    }
}