code/precompiles/src/crowdloan.rs

crowdloan.rs

670 lines · 25,923 bytes · 19a6485969RawGitHub
use alloc::string::String;
use core::marker::PhantomData;

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;
use pallet_evm::PrecompileHandle;
use pallet_subtensor_proxy as pallet_proxy;
use precompile_utils::prelude::Address;
use precompile_utils::{EvmResult, solidity::Codec};
use sp_core::{ByteArray, H256};
use sp_runtime::traits::{AsSystemOriginSigner, Dispatchable, UniqueSaturatedInto};

use crate::{PrecompileExt, PrecompileHandleExt};

pub struct CrowdloanPrecompile<R>(PhantomData<R>);

impl<R> PrecompileExt<R::AccountId> for CrowdloanPrecompile<R>
where
    R: frame_system::Config
        + pallet_balances::Config
        + pallet_crowdloan::Config
        + pallet_evm::Config
        + pallet_proxy::Config
        + pallet_subtensor::Config
        + pallet_shield::Config
        + pallet_subtensor_proxy::Config
        + Send
        + Sync
        + scale_info::TypeInfo,
    R::AccountId: From<[u8; 32]> + ByteArray,
    <R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
    <R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::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>,
{
    const INDEX: u64 = 2057;
}

#[precompile_utils::precompile]
impl<R> CrowdloanPrecompile<R>
where
    R: frame_system::Config
        + pallet_balances::Config
        + pallet_crowdloan::Config
        + pallet_evm::Config
        + pallet_proxy::Config
        + pallet_subtensor::Config
        + pallet_shield::Config
        + pallet_subtensor_proxy::Config
        + Send
        + Sync
        + scale_info::TypeInfo,
    R::AccountId: From<[u8; 32]> + ByteArray,
    <R as frame_system::Config>::RuntimeOrigin: AsSystemOriginSigner<R::AccountId> + Clone,
    <R as frame_system::Config>::RuntimeCall: From<pallet_crowdloan::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>::RuntimeCall: From<pallet_crowdloan::Call<R>>
        + GetDispatchInfo
        + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
    <R as pallet_evm::Config>::AddressMapping: AddressMapping<R::AccountId>,
{
    #[precompile::public("getCrowdloan(uint32)")]
    #[precompile::view]
    fn get_crowdloan(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
    ) -> EvmResult<CrowdloanInfo> {
        handle.record_db_reads::<R>(1)?;
        let crowdloan = pallet_crowdloan::Crowdloans::<R>::get(crowdloan_id).ok_or(
            PrecompileFailure::Error {
                exit_status: ExitError::Other("Crowdloan not found".into()),
            },
        )?;

        Ok(CrowdloanInfo {
            creator: H256::from_slice(crowdloan.creator.as_slice()),
            deposit: u64::from(crowdloan.deposit),
            min_contribution: u64::from(crowdloan.min_contribution),
            end: crowdloan.end.unique_saturated_into(),
            cap: u64::from(crowdloan.cap),
            funds_account: H256::from_slice(crowdloan.funds_account.as_slice()),
            raised: u64::from(crowdloan.raised),
            has_target_address: crowdloan.target_address.is_some(),
            target_address: crowdloan
                .target_address
                .map(|a| H256::from_slice(a.as_slice()))
                .unwrap_or_else(H256::zero),
            finalized: crowdloan.finalized,
            contributors_count: crowdloan.contributors_count,
        })
    }

    #[precompile::public("getContribution(uint32,bytes32)")]
    #[precompile::view]
    fn get_contribution(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
        coldkey: H256,
    ) -> EvmResult<u64> {
        handle.record_db_reads::<R>(1)?;
        let coldkey = R::AccountId::from(coldkey.0);
        let contribution = pallet_crowdloan::Contributions::<R>::get(crowdloan_id, coldkey).ok_or(
            PrecompileFailure::Error {
                exit_status: ExitError::Other("Crowdloan or contribution not found".into()),
            },
        )?;

        Ok(u64::from(contribution))
    }

    #[precompile::public("create(uint64,uint64,uint64,uint32,address)")]
    #[precompile::payable]
    fn create(
        handle: &mut impl PrecompileHandle,
        deposit: u64,
        min_contribution: u64,
        cap: u64,
        end: u32,
        target_address: Address,
    ) -> EvmResult<()> {
        let who = handle.caller_account_id::<R>();
        let target_address = R::AddressMapping::into_account_id(target_address.0);
        let call = pallet_crowdloan::Call::<R>::create {
            deposit: deposit.into(),
            min_contribution: min_contribution.into(),
            cap: cap.into(),
            end: end.into(),
            call: None,
            target_address: Some(target_address),
        };

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

    #[precompile::public("contribute(uint32,uint64)")]
    #[precompile::payable]
    fn contribute(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
        amount: u64,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::contribute {
            crowdloan_id,
            amount: amount.into(),
        };

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

    #[precompile::public("withdraw(uint32)")]
    #[precompile::payable]
    fn withdraw(handle: &mut impl PrecompileHandle, crowdloan_id: u32) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::withdraw { crowdloan_id };

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

    #[precompile::public("finalize(uint32)")]
    #[precompile::payable]
    fn finalize(handle: &mut impl PrecompileHandle, crowdloan_id: u32) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::finalize { crowdloan_id };

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

    #[precompile::public("refund(uint32)")]
    #[precompile::payable]
    fn refund(handle: &mut impl PrecompileHandle, crowdloan_id: u32) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::refund { crowdloan_id };

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

    #[precompile::public("dissolve(uint32)")]
    #[precompile::payable]
    fn dissolve(handle: &mut impl PrecompileHandle, crowdloan_id: u32) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::dissolve { crowdloan_id };

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

    #[precompile::public("updateMinContribution(uint32,uint64)")]
    #[precompile::payable]
    fn update_min_contribution(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
        new_min_contribution: u64,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::update_min_contribution {
            crowdloan_id,
            new_min_contribution: new_min_contribution.into(),
        };

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

    #[precompile::public("updateEnd(uint32,uint32)")]
    #[precompile::payable]
    fn update_end(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
        new_end: u32,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::update_end {
            crowdloan_id,
            new_end: new_end.into(),
        };

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

    #[precompile::public("updateCap(uint32,uint64)")]
    #[precompile::payable]
    fn update_cap(
        handle: &mut impl PrecompileHandle,
        crowdloan_id: u32,
        new_cap: u64,
    ) -> EvmResult<()> {
        let account_id = handle.caller_account_id::<R>();
        let call = pallet_crowdloan::Call::<R>::update_cap {
            crowdloan_id,
            new_cap: new_cap.into(),
        };

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

#[derive(Codec)]
struct CrowdloanInfo {
    creator: H256,
    deposit: u64,
    min_contribution: u64,
    end: u32,
    cap: u64,
    funds_account: H256,
    raised: u64,
    has_target_address: bool,
    target_address: H256,
    finalized: bool,
    contributors_count: u32,
}

#[cfg(test)]
mod tests {
    #![allow(clippy::expect_used, clippy::arithmetic_side_effects)]

    use super::*;
    use crate::PrecompileExt;
    use crate::mock::{
        AccountId, Runtime, RuntimeOrigin, System, addr_from_index, fund_account, mapped_account,
        new_test_ext, precompiles, selector_u32,
    };
    use precompile_utils::solidity::{codec::Address, encode_return_value, encode_with_selector};
    use precompile_utils::testing::PrecompileTesterExt;
    use sp_core::H160;

    const CREATOR_DEPOSIT: u64 = 50;
    const MIN_CONTRIBUTION: u64 = 10;
    const CAP: u64 = 300;
    const END: u32 = 50;
    const ACCOUNT_BALANCE: u64 = 1_000;

    fn get_crowdloan(caller: H160, crowdloan_id: u32, expected: CrowdloanInfo) {
        let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

        precompiles::<CrowdloanPrecompile<Runtime>>()
            .prepare_test(
                caller,
                precompile_addr,
                encode_with_selector(selector_u32("getCrowdloan(uint32)"), (crowdloan_id,)),
            )
            .with_static_call(true)
            .execute_returns_raw(encode_return_value(expected));
    }

    fn expected_crowdloan_info(crowdloan_id: u32) -> CrowdloanInfo {
        let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
            .expect("crowdloan should exist");

        CrowdloanInfo {
            creator: H256::from_slice(crowdloan.creator.as_slice()),
            deposit: u64::from(crowdloan.deposit),
            min_contribution: u64::from(crowdloan.min_contribution),
            end: crowdloan.end as u32,
            cap: u64::from(crowdloan.cap),
            funds_account: H256::from_slice(crowdloan.funds_account.as_slice()),
            raised: u64::from(crowdloan.raised),
            has_target_address: crowdloan.target_address.is_some(),
            target_address: crowdloan
                .target_address
                .map(|account| H256::from_slice(account.as_slice()))
                .unwrap_or_else(H256::zero),
            finalized: crowdloan.finalized,
            contributors_count: crowdloan.contributors_count,
        }
    }

    fn create_crowdloan(caller: H160, target: H160) -> u32 {
        let crowdloan_id = pallet_crowdloan::NextCrowdloanId::<Runtime>::get();
        let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

        precompiles::<CrowdloanPrecompile<Runtime>>()
            .prepare_test(
                caller,
                precompile_addr,
                encode_with_selector(
                    selector_u32("create(uint64,uint64,uint64,uint32,address)"),
                    (CREATOR_DEPOSIT, MIN_CONTRIBUTION, CAP, END, Address(target)),
                ),
            )
            .execute_returns(());
        crowdloan_id
    }

    fn contribute(caller: H160, crowdloan_id: u32, amount: u64) {
        let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

        precompiles::<CrowdloanPrecompile<Runtime>>()
            .prepare_test(
                caller,
                precompile_addr,
                encode_with_selector(
                    selector_u32("contribute(uint32,uint64)"),
                    (crowdloan_id, amount),
                ),
            )
            .execute_returns(());
    }

    fn withdraw(caller: H160, crowdloan_id: u32) {
        let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

        precompiles::<CrowdloanPrecompile<Runtime>>()
            .prepare_test(
                caller,
                precompile_addr,
                encode_with_selector(selector_u32("withdraw(uint32)"), (crowdloan_id,)),
            )
            .execute_returns(());
    }

    fn finalize(caller: H160, crowdloan_id: u32) {
        let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

        precompiles::<CrowdloanPrecompile<Runtime>>()
            .prepare_test(
                caller,
                precompile_addr,
                encode_with_selector(selector_u32("finalize(uint32)"), (crowdloan_id,)),
            )
            .execute_returns(());
    }

    #[test]
    fn crowdloan_precompile_reads_existing_pallet_crowdloan() {
        new_test_ext().execute_with(|| {
            let creator = AccountId::from([0x11; 32]);
            let target = AccountId::from([0x12; 32]);
            let caller = addr_from_index(0x7001);
            let crowdloan_id = pallet_crowdloan::NextCrowdloanId::<Runtime>::get();

            fund_account(&creator, ACCOUNT_BALANCE);
            pallet_crowdloan::Pallet::<Runtime>::create(
                RuntimeOrigin::signed(creator),
                CREATOR_DEPOSIT.into(),
                MIN_CONTRIBUTION.into(),
                CAP.into(),
                END.into(),
                None,
                Some(target),
            )
            .expect("direct crowdloan create should work");

            get_crowdloan(caller, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }

    #[test]
    fn crowdloan_precompile_creates_and_reads_crowdloan() {
        new_test_ext().execute_with(|| {
            let creator = addr_from_index(0x7002);
            let target = addr_from_index(0x7003);
            let creator_account = mapped_account(creator);
            let target_account = mapped_account(target);

            fund_account(&creator_account, ACCOUNT_BALANCE);

            let crowdloan_id = create_crowdloan(creator, target);
            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");

            assert_eq!(crowdloan.creator, creator_account);
            assert_eq!(u64::from(crowdloan.deposit), CREATOR_DEPOSIT);
            assert_eq!(u64::from(crowdloan.min_contribution), MIN_CONTRIBUTION);
            assert_eq!(u64::from(crowdloan.cap), CAP);
            assert_eq!(crowdloan.end, END as u64);
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT);
            assert_eq!(crowdloan.target_address, Some(target_account));
            assert!(!crowdloan.finalized);
            assert_eq!(crowdloan.contributors_count, 1);
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }

    #[test]
    fn crowdloan_precompile_contributes_and_withdraws() {
        new_test_ext().execute_with(|| {
            let creator = addr_from_index(0x7004);
            let contributor = addr_from_index(0x7005);
            let target = addr_from_index(0x7006);
            let creator_account = mapped_account(creator);
            let contributor_account = mapped_account(contributor);
            let contribution = 30_u64;

            fund_account(&creator_account, ACCOUNT_BALANCE);
            fund_account(&contributor_account, ACCOUNT_BALANCE);

            let crowdloan_id = create_crowdloan(creator, target);
            contribute(contributor, crowdloan_id, contribution);

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT + contribution);
            assert_eq!(crowdloan.contributors_count, 2);
            assert_eq!(
                pallet_crowdloan::Contributions::<Runtime>::get(
                    crowdloan_id,
                    &contributor_account,
                ),
                Some(contribution.into()),
            );
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));

            withdraw(contributor, crowdloan_id);

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT);
            assert_eq!(crowdloan.contributors_count, 1);
            assert_eq!(
                pallet_crowdloan::Contributions::<Runtime>::get(
                    crowdloan_id,
                    &contributor_account,
                ),
                None,
            );
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }

    #[test]
    fn crowdloan_precompile_contributes_and_withdraws_from_pallet_crowdloan() {
        new_test_ext().execute_with(|| {
            let creator = AccountId::from([0x22; 32]);
            let target = AccountId::from([0x23; 32]);
            let contributor = addr_from_index(0x7016);
            let contributor_account = mapped_account(contributor);
            let crowdloan_id = pallet_crowdloan::NextCrowdloanId::<Runtime>::get();
            let contribution = 30_u64;

            fund_account(&creator, ACCOUNT_BALANCE);
            fund_account(&contributor_account, ACCOUNT_BALANCE);
            pallet_crowdloan::Pallet::<Runtime>::create(
                RuntimeOrigin::signed(creator),
                CREATOR_DEPOSIT.into(),
                MIN_CONTRIBUTION.into(),
                CAP.into(),
                END.into(),
                None,
                Some(target),
            )
            .expect("direct crowdloan create should work");

            contribute(contributor, crowdloan_id, contribution);

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT + contribution);
            assert_eq!(crowdloan.contributors_count, 2);
            get_crowdloan(contributor, crowdloan_id, expected_crowdloan_info(crowdloan_id));

            withdraw(contributor, crowdloan_id);

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT);
            assert_eq!(crowdloan.contributors_count, 1);
            assert_eq!(
                pallet_crowdloan::Contributions::<Runtime>::get(
                    crowdloan_id,
                    &contributor_account,
                ),
                None,
            );
            get_crowdloan(contributor, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }

    #[test]
    fn crowdloan_precompile_finalizes_capped_crowdloan() {
        new_test_ext().execute_with(|| {
            let creator = addr_from_index(0x7007);
            let contributor = addr_from_index(0x7008);
            let target = addr_from_index(0x7009);
            let creator_account = mapped_account(creator);
            let contributor_account = mapped_account(contributor);
            let target_account = mapped_account(target);

            fund_account(&creator_account, ACCOUNT_BALANCE);
            fund_account(&contributor_account, ACCOUNT_BALANCE);

            let crowdloan_id = create_crowdloan(creator, target);
            contribute(contributor, crowdloan_id, CAP - CREATOR_DEPOSIT);
            System::set_block_number(END.into());
            finalize(creator, crowdloan_id);

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert!(crowdloan.finalized);
            assert_eq!(
                pallet_balances::Pallet::<Runtime>::free_balance(&target_account),
                CAP.into(),
            );
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }

    #[test]
    fn crowdloan_precompile_refunds_and_dissolves_crowdloan() {
        new_test_ext().execute_with(|| {
            let creator = addr_from_index(0x7010);
            let first = addr_from_index(0x7011);
            let second = addr_from_index(0x7012);
            let target = addr_from_index(0x7013);
            let creator_account = mapped_account(creator);
            let first_account = mapped_account(first);
            let second_account = mapped_account(second);
            let contribution = 30_u64;

            fund_account(&creator_account, ACCOUNT_BALANCE);
            fund_account(&first_account, ACCOUNT_BALANCE);
            fund_account(&second_account, ACCOUNT_BALANCE);

            let crowdloan_id = create_crowdloan(creator, target);
            contribute(first, crowdloan_id, contribution);
            contribute(second, crowdloan_id, contribution);
            System::set_block_number(END.into());
            let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

            precompiles::<CrowdloanPrecompile<Runtime>>()
                .prepare_test(
                    creator,
                    precompile_addr,
                    encode_with_selector(selector_u32("refund(uint32)"), (crowdloan_id,)),
                )
                .execute_returns(());

            let crowdloan = pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                .expect("crowdloan should exist");
            assert_eq!(u64::from(crowdloan.raised), CREATOR_DEPOSIT);
            assert_eq!(crowdloan.contributors_count, 1);
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));

            precompiles::<CrowdloanPrecompile<Runtime>>()
                .prepare_test(
                    creator,
                    precompile_addr,
                    encode_with_selector(selector_u32("dissolve(uint32)"), (crowdloan_id,)),
                )
                .execute_returns(());

            assert!(pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id).is_none());
        });
    }

    #[test]
    fn crowdloan_precompile_updates_crowdloan_terms() {
        new_test_ext().execute_with(|| {
            let creator = addr_from_index(0x7014);
            let target = addr_from_index(0x7015);
            let creator_account = mapped_account(creator);
            let new_min_contribution = 20_u64;
            let new_end = 80_u32;
            let new_cap = 400_u64;

            fund_account(&creator_account, ACCOUNT_BALANCE);

            let crowdloan_id = create_crowdloan(creator, target);
            let precompiles = precompiles::<CrowdloanPrecompile<Runtime>>();
            let precompile_addr = addr_from_index(CrowdloanPrecompile::<Runtime>::INDEX);

            precompiles
                .prepare_test(
                    creator,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("updateMinContribution(uint32,uint64)"),
                        (crowdloan_id, new_min_contribution),
                    ),
                )
                .execute_returns(());
            assert_eq!(
                u64::from(
                    pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                        .expect("crowdloan should exist")
                        .min_contribution,
                ),
                new_min_contribution,
            );

            precompiles
                .prepare_test(
                    creator,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("updateEnd(uint32,uint32)"),
                        (crowdloan_id, new_end),
                    ),
                )
                .execute_returns(());
            assert_eq!(
                pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                    .expect("crowdloan should exist")
                    .end,
                new_end as u64,
            );

            precompiles
                .prepare_test(
                    creator,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("updateCap(uint32,uint64)"),
                        (crowdloan_id, new_cap),
                    ),
                )
                .execute_returns(());
            assert_eq!(
                u64::from(
                    pallet_crowdloan::Crowdloans::<Runtime>::get(crowdloan_id)
                        .expect("crowdloan should exist")
                        .cap,
                ),
                new_cap,
            );
            get_crowdloan(creator, crowdloan_id, expected_crowdloan_info(crowdloan_id));
        });
    }
}