git.delta.rocks / unique-network / refs/commits / 982476ef99fe

difftreelog

feautres: Pending interval is now tied to relay blocks, contract sponsors and `stopAppPromotion` added. Preparing to integrate the `app-promotion` palette to integrate with Unique and Quartz.

PraetorP2022-08-29parent: #9d0f344.patch.diff
in: master

13 files changed

modifiedpallets/app-promotion/src/lib.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/lib.rs
+++ b/pallets/app-promotion/src/lib.rs
@@ -37,9 +37,10 @@
 pub mod weights;
 
 use sp_std::{vec::Vec, iter::Sum, borrow::ToOwned};
+use sp_core::H160;
 use codec::EncodeLike;
 use pallet_balances::BalanceLock;
-pub use types::ExtendedLockableCurrency;
+pub use types::*;
 
 // use up_common::constants::{DAYS, UNIQUE};
 use up_data_structs::CollectionId;
@@ -78,7 +79,6 @@
 	use super::*;
 	use frame_support::{Blake2_128Concat, Twox64Concat, pallet_prelude::*, storage::Key, PalletId};
 	use frame_system::pallet_prelude::*;
-	use types::CollectionHandler;
 
 	#[pallet::config]
 	pub trait Config: frame_system::Config + pallet_evm::account::Config {
@@ -89,6 +89,8 @@
 			CollectionId = CollectionId,
 		>;
 
+		type ContractHandler: ContractHandler<AccountId = Self::CrossAccountId, ContractId = H160>;
+
 		type TreasuryAccountId: Get<Self::AccountId>;
 
 		/// The app's pallet id, used for deriving its sovereign account ID.
@@ -98,7 +100,7 @@
 		/// In relay blocks.
 		#[pallet::constant]
 		type RecalculationInterval: Get<Self::BlockNumber>;
-		/// In chain blocks.
+		/// In relay blocks.
 		#[pallet::constant]
 		type PendingInterval: Get<Self::BlockNumber>;
 
@@ -120,13 +122,6 @@
 
 		/// Events compatible with [`frame_system::Config::Event`].
 		type Event: IsType<<Self as frame_system::Config>::Event> + From<Event<Self>>;
-
-		// /// Number of blocks that pass between treasury balance updates due to inflation
-		// #[pallet::constant]
-		// type InterestBlockInterval: Get<Self::BlockNumber>;
-
-		// // Weight information for functions of this pallet.
-		// type WeightInfo: WeightInfo;
 	}
 
 	#[pallet::pallet]
@@ -146,13 +141,14 @@
 
 	#[pallet::error]
 	pub enum Error<T> {
+		/// Error due to action requiring admin to be set
 		AdminNotSet,
-		/// No permission to perform action
+		/// No permission to perform an action
 		NoPermission,
 		/// Insufficient funds to perform an action
 		NotSufficientFounds,
+		/// An error related to the fact that an invalid argument was passed to perform an action
 		InvalidArgument,
-		AlreadySponsored,
 	}
 
 	#[pallet::storage]
@@ -183,7 +179,7 @@
 		QueryKind = ValueQuery,
 	>;
 
-	/// A block when app-promotion has started
+	/// A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
 	#[pallet::storage]
 	pub type StartBlock<T: Config> = StorageValue<Value = T::BlockNumber, QueryKind = ValueQuery>;
 
@@ -285,6 +281,21 @@
 			Ok(())
 		}
 
+		#[pallet::weight(0)]
+		pub fn stop_app_promotion(origin: OriginFor<T>) -> DispatchResult
+		where
+			<T as frame_system::Config>::BlockNumber: From<u32>,
+		{
+			ensure_root(origin)?;
+
+			if <StartBlock<T>>::get() != 0u32.into() {
+				<StartBlock<T>>::set(T::BlockNumber::default());
+				<NextInterestBlock<T>>::set(T::BlockNumber::default());
+			}
+
+			Ok(())
+		}
+
 		#[pallet::weight(T::WeightInfo::stake())]
 		pub fn stake(staker: OriginFor<T>, amount: BalanceOf<T>) -> DispatchResult {
 			let staker_id = ensure_signed(staker)?;
@@ -341,7 +352,8 @@
 					.ok_or(ArithmeticError::Underflow)?,
 			);
 
-			let block = frame_system::Pallet::<T>::block_number() + T::PendingInterval::get();
+			let block =
+				T::RelayBlockNumberProvider::current_block_number() + T::PendingInterval::get();
 			<PendingUnstake<T>>::insert(
 				(&staker_id, block),
 				<PendingUnstake<T>>::get((&staker_id, block))
@@ -415,113 +427,46 @@
 			);
 			T::CollectionHandler::remove_collection_sponsor(collection_id)
 		}
-	}
-}
-
-impl<T: Config> Pallet<T> {
-	// pub fn stake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
-	// 	let balance = <<T as Config>::Currency as Currency<T::AccountId>>::free_balance(staker);
-
-	// 	ensure!(balance >= amount, ArithmeticError::Underflow);
-
-	// 	Self::set_lock_unchecked(staker, amount);
-
-	// 	let block_number = <T::BlockNumberProvider as BlockNumberProvider>::current_block_number();
 
-	// 	<Staked<T>>::insert(
-	// 		(staker, block_number),
-	// 		<Staked<T>>::get((staker, block_number))
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	<TotalStaked<T>>::set(
-	// 		<TotalStaked<T>>::get()
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	Ok(())
-	// }
-
-	// pub fn unstake(staker: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
-	// 	let mut stakes = Staked::<T>::iter_prefix((staker,)).collect::<Vec<_>>();
-
-	// 	let total_staked = stakes
-	// 		.iter()
-	// 		.fold(<BalanceOf<T>>::default(), |acc, (_, amount)| acc + *amount);
-
-	// 	ensure!(total_staked >= amount, ArithmeticError::Underflow);
-
-	// 	<TotalStaked<T>>::set(
-	// 		<TotalStaked<T>>::get()
-	// 			.checked_sub(&amount)
-	// 			.ok_or(ArithmeticError::Underflow)?,
-	// 	);
-
-	// 	let block = <T::BlockNumberProvider>::current_block_number() + WEEK.into();
-	// 	<PendingUnstake<T>>::insert(
-	// 		(staker, block),
-	// 		<PendingUnstake<T>>::get((staker, block))
-	// 			.checked_add(&amount)
-	// 			.ok_or(ArithmeticError::Overflow)?,
-	// 	);
-
-	// 	stakes.sort_by_key(|(block, _)| *block);
-
-	// 	let mut acc_amount = amount;
-	// 	let new_state = stakes
-	// 		.into_iter()
-	// 		.map_while(|(block, balance_per_block)| {
-	// 			if acc_amount == <BalanceOf<T>>::default() {
-	// 				return None;
-	// 			}
-	// 			if acc_amount <= balance_per_block {
-	// 				let res = (block, balance_per_block - acc_amount, acc_amount);
-	// 				acc_amount = <BalanceOf<T>>::default();
-	// 				return Some(res);
-	// 			} else {
-	// 				acc_amount -= balance_per_block;
-	// 				return Some((block, <BalanceOf<T>>::default(), acc_amount));
-	// 			}
-	// 		})
-	// 		.collect::<Vec<_>>();
-
-	// 	new_state
-	// 		.into_iter()
-	// 		.for_each(|(block, to_staked, _to_pending)| {
-	// 			if to_staked == <BalanceOf<T>>::default() {
-	// 				<Staked<T>>::remove((staker, block));
-	// 			} else {
-	// 				<Staked<T>>::insert((staker, block), to_staked);
-	// 			}
-	// 		});
+		#[pallet::weight(0)]
+		pub fn sponsor_conract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
 
-	// 	Ok(())
-	// }
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
 
-	// pub fn sponsor_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
-	// 	Ok(())
-	// }
+			T::ContractHandler::set_sponsor(
+				T::CrossAccountId::from_sub(Self::account_id()),
+				contract_id,
+			)
+		}
 
-	// pub fn stop_sponsorign_collection(admin: T::AccountId, collection_id: u32) -> DispatchResult {
-	// 	Ok(())
-	// }
+		#[pallet::weight(0)]
+		pub fn stop_sponsorign_contract(admin: OriginFor<T>, contract_id: H160) -> DispatchResult {
+			let admin_id = ensure_signed(admin)?;
 
-	pub fn sponsor_conract(admin: T::AccountId, app_id: u32) -> DispatchResult {
-		Ok(())
-	}
+			ensure!(
+				admin_id == Admin::<T>::get().ok_or(Error::<T>::AdminNotSet)?,
+				Error::<T>::NoPermission
+			);
 
-	pub fn stop_sponsorign_contract(admin: T::AccountId, app_id: u32) -> DispatchResult {
-		Ok(())
+			ensure!(
+				T::ContractHandler::get_sponsor(contract_id)?.ok_or(<Error<T>>::InvalidArgument)?
+					== T::CrossAccountId::from_sub(Self::account_id()),
+				<Error<T>>::NoPermission
+			);
+			T::ContractHandler::remove_contract_sponsor(contract_id)
+		}
 	}
+}
 
+impl<T: Config> Pallet<T> {
 	pub fn account_id() -> T::AccountId {
 		T::PalletId::get().into_account_truncating()
 	}
-}
 
-impl<T: Config> Pallet<T> {
 	fn unlock_balance_unchecked(staker: &T::AccountId, amount: BalanceOf<T>) {
 		let mut locked_balance = Self::get_locked_balance(staker).map(|l| l.amount).unwrap();
 		locked_balance -= amount;
modifiedpallets/app-promotion/src/types.rsdiffbeforeafterboth
--- a/pallets/app-promotion/src/types.rs
+++ b/pallets/app-promotion/src/types.rs
@@ -9,7 +9,7 @@
 use sp_runtime::DispatchError;
 use up_data_structs::{CollectionId, SponsorshipState};
 use sp_std::borrow::ToOwned;
-
+use pallet_evm_contract_helpers::{Pallet as EvmHelpersPallet, Config as EvmHelpersConfig, Sponsoring};
 
 pub trait ExtendedLockableCurrency<AccountId: Parameter>: LockableCurrency<AccountId> {
 	fn locks<KArg>(who: KArg) -> WeakBoundedVec<BalanceLock<Self::Balance>, Self::MaxLocks>
@@ -94,3 +94,40 @@
 			.map(|acc| acc.to_owned()))
 	}
 }
+
+pub trait ContractHandler {
+	type ContractId;
+	type AccountId;
+
+	fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult;
+
+	fn remove_contract_sponsor(collection_id: Self::ContractId) -> DispatchResult;
+
+	fn get_sponsor(contract_id: Self::ContractId)
+		-> Result<Option<Self::AccountId>, DispatchError>;
+}
+
+impl<T: EvmHelpersConfig> ContractHandler for EvmHelpersPallet<T> {
+	type ContractId = sp_core::H160;
+
+	type AccountId = T::CrossAccountId;
+
+	fn set_sponsor(sponsor_id: Self::AccountId, contract_id: Self::ContractId) -> DispatchResult {
+		Sponsoring::<T>::insert(
+			contract_id,
+			SponsorshipState::<T::CrossAccountId>::Confirmed(sponsor_id),
+		);
+		Ok(())
+	}
+
+	fn remove_contract_sponsor(contract_id: Self::ContractId) -> DispatchResult {
+		Sponsoring::<T>::remove(contract_id);
+		Ok(())
+	}
+
+	fn get_sponsor(
+		contract_id: Self::ContractId,
+	) -> Result<Option<Self::AccountId>, DispatchError> {
+		Ok(Self::get_sponsor(contract_id))
+	}
+}
modifiedpallets/evm-contract-helpers/src/lib.rsdiffbeforeafterboth
--- a/pallets/evm-contract-helpers/src/lib.rs
+++ b/pallets/evm-contract-helpers/src/lib.rs
@@ -75,7 +75,7 @@
 	/// * **Key** - contract address.
 	/// * **Value** - sponsorship state.
 	#[pallet::storage]
-	pub(super) type Sponsoring<T: Config> = StorageMap<
+	pub type Sponsoring<T: Config> = StorageMap<
 		Hasher = Twox64Concat,
 		Key = H160,
 		Value = SponsorshipState<T::CrossAccountId>,
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
--- a/primitives/common/src/constants.rs
+++ b/primitives/common/src/constants.rs
@@ -22,6 +22,7 @@
 use crate::types::{BlockNumber, Balance};
 
 pub const MILLISECS_PER_BLOCK: u64 = 12000;
+pub const MILLISECS_PER_RELAY_BLOCK: u64 = 6000;
 
 pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK;
 
@@ -30,6 +31,11 @@
 pub const HOURS: BlockNumber = MINUTES * 60;
 pub const DAYS: BlockNumber = HOURS * 24;
 
+// These time units are defined in number of relay blocks.
+pub const RELAY_MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_RELAY_BLOCK as BlockNumber);
+pub const RELAY_HOURS: BlockNumber = RELAY_MINUTES * 60;
+pub const RELAY_DAYS: BlockNumber = RELAY_HOURS * 24;
+
 pub const MICROUNIQUE: Balance = 1_000_000_000_000;
 pub const MILLIUNIQUE: Balance = 1_000 * MICROUNIQUE;
 pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE;
modifiedruntime/common/config/pallets/app_promotion.rsdiffbeforeafterboth
--- a/runtime/common/config/pallets/app_promotion.rs
+++ b/runtime/common/config/pallets/app_promotion.rs
@@ -16,28 +16,40 @@
 
 use crate::{
 	runtime_common::config::pallets::{TreasuryAccountId, RelayChainBlockNumberProvider},
-	Runtime, Balances, BlockNumber, Unique, Event,
+	Runtime, Balances, BlockNumber, Unique, Event, EvmContractHelpers,
 };
 
 use frame_support::{parameter_types, PalletId};
 use sp_arithmetic::Perbill;
 use up_common::{
-	constants::{DAYS, UNIQUE},
+	constants::{DAYS, UNIQUE, RELAY_DAYS},
 	types::Balance,
 };
 
+#[cfg(all(not(feature = "unique-runtime"), not(feature = "quartz-runtime")))]
 parameter_types! {
 	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
 	pub const RecalculationInterval: BlockNumber = 20;
-	pub const PendingInterval: BlockNumber = 10;
+	pub const PendingInterval: BlockNumber = 20;
 	pub const Nominal: Balance = UNIQUE;
 	pub const Day: BlockNumber = DAYS;
-	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), 2 * DAYS) * Perbill::from_rational(5u32, 10_000);
+	pub IntervalIncome: Perbill = Perbill::from_rational(RecalculationInterval::get(), RELAY_DAYS) * Perbill::from_rational(5u32, 10_000);
+}
+
+#[cfg(any(feature = "unique-runtime", feature = "quartz-runtime"))]
+parameter_types! {
+	pub const AppPromotionId: PalletId = PalletId(*b"appstake");
+	pub const RecalculationInterval: BlockNumber = RELAY_DAYS;
+	pub const PendingInterval: BlockNumber = 7 * RELAY_DAYS;
+	pub const Nominal: Balance = UNIQUE;
+	pub const Day: BlockNumber = RELAY_DAYS;
+	pub IntervalIncome: Perbill = Perbill::from_rational(5u32, 10_000);
 }
 
 impl pallet_app_promotion::Config for Runtime {
 	type PalletId = AppPromotionId;
 	type CollectionHandler = Unique;
+	type ContractHandler = EvmContractHelpers;
 	type Currency = Balances;
 	type WeightInfo = pallet_app_promotion::weights::SubstrateWeight<Self>;
 	type TreasuryAccountId = TreasuryAccountId;
modifiedtests/src/app-promotion.test.tsdiffbeforeafterboth
--- a/tests/src/app-promotion.test.ts
+++ b/tests/src/app-promotion.test.ts
@@ -706,6 +706,12 @@
       nominal = helper.balance.getOneTokenNominal();
     });
   });
+
+  after(async function () {
+    await usingPlaygrounds(async (helper) => {
+      await helper.signTransaction(alice, helper.api!.tx.sudo.sudo(helper.api!.tx.promotion.stopAppPromotion()));
+    });
+  });
   
   it('will credit 0.05% for staking period', async () => {
     // arrange: bob.stake(10000);
@@ -770,39 +776,24 @@
       const staker = await createUser(40n * nominal);
       
       await waitForRecalculationBlock(helper.api!);
-      // const foo = await helper.api!.registry.getChainProperties().
+      
 
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // await waitNewBlocks(helper.api!, 1);
+      
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // await waitNewBlocks(helper.api!, 1);
+      
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.stake(10n * nominal))).to.be.eventually.fulfilled;
-      // console.log(await helper.balance.getSubstrate(staker.address));
-      // await waitNewBlocks(helper.api!, 17);
+      
       await waitForRelayBlock(helper.api!, 34);
       expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
         .map(([_, amount]) => amount.toBigInt()))
         .to.be.deep.equal([calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n), calculateIncome(10n * nominal, 10n)]);
       
-      // console.log(await getBlockNumber(helper.api!));
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([block, amount]) => [block.toBigInt(), amount.toBigInt()]));
-      // console.log(`${calculateIncome(10n * nominal, 10n)} || ${calculateIncome(10n * nominal, 10n, 2)}`);
-      // await waitNewBlocks(helper.api!, 10);
       await waitForRelayBlock(helper.api!, 20);
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
-      // console.log(await helper.balance.getSubstrate(staker.address));
       await expect(helper.signTransaction(staker, helper.api!.tx.promotion.unstake(calculateIncome(10n * nominal, 10n, 2) - 10n * nominal))).to.be.eventually.fulfilled;
-      // console.log(calculateIncome(10n * nominal, 10n, 2));
-      // console.log(calculateIncome(10n * nominal, 10n, 3));
-      // console.log(calculateIncome(10n * nominal, 10n, 4));
-      // console.log(calculateIncome(10n * nominal, 10n, 5));
       expect((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker)))
         .map(([_, amount]) => amount.toBigInt()))
         .to.be.deep.equal([10n * nominal, calculateIncome(10n * nominal, 10n, 2), calculateIncome(10n * nominal, 10n, 2)]);
-      
-      // console.log((await helper.api!.rpc.unique.totalStakedPerBlock(normalizeAccountId(staker))).map(([_, amount]) => amount.toBigInt()));
-      
-      // console.log(await helper.balance.getSubstrate(staker.address));
     });
     
   });
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -78,7 +78,7 @@
        **/
       palletId: FrameSupportPalletId & AugmentedConst<ApiType>;
       /**
-       * In chain blocks.
+       * In relay blocks.
        **/
       pendingInterval: u32 & AugmentedConst<ApiType>;
       /**
modifiedtests/src/interfaces/augment-api-errors.tsdiffbeforeafterboth
before · tests/src/interfaces/augment-api-errors.ts
1// Auto-generated via `yarn polkadot-types-from-chain`, do not edit2/* eslint-disable */34// import type lookup before we augment - in some environments5// this is required to allow for ambient/previous definitions6import '@polkadot/api-base/types/errors';78import type { ApiTypes, AugmentedError } from '@polkadot/api-base/types';910export type __AugmentedError<ApiType extends ApiTypes> = AugmentedError<ApiType>;1112declare module '@polkadot/api-base/types/errors' {13  interface AugmentedErrors<ApiType extends ApiTypes> {14    balances: {15      /**16       * Beneficiary account must pre-exist17       **/18      DeadAccount: AugmentedError<ApiType>;19      /**20       * Value too low to create account due to existential deposit21       **/22      ExistentialDeposit: AugmentedError<ApiType>;23      /**24       * A vesting schedule already exists for this account25       **/26      ExistingVestingSchedule: AugmentedError<ApiType>;27      /**28       * Balance too low to send value29       **/30      InsufficientBalance: AugmentedError<ApiType>;31      /**32       * Transfer/payment would kill account33       **/34      KeepAlive: AugmentedError<ApiType>;35      /**36       * Account liquidity restrictions prevent withdrawal37       **/38      LiquidityRestrictions: AugmentedError<ApiType>;39      /**40       * Number of named reserves exceed MaxReserves41       **/42      TooManyReserves: AugmentedError<ApiType>;43      /**44       * Vesting balance too high to send value45       **/46      VestingBalance: AugmentedError<ApiType>;47      /**48       * Generic error49       **/50      [key: string]: AugmentedError<ApiType>;51    };52    common: {53      /**54       * Account token limit exceeded per collection55       **/56      AccountTokenLimitExceeded: AugmentedError<ApiType>;57      /**58       * Can't transfer tokens to ethereum zero address59       **/60      AddressIsZero: AugmentedError<ApiType>;61      /**62       * Address is not in allow list.63       **/64      AddressNotInAllowlist: AugmentedError<ApiType>;65      /**66       * Requested value is more than the approved67       **/68      ApprovedValueTooLow: AugmentedError<ApiType>;69      /**70       * Tried to approve more than owned71       **/72      CantApproveMoreThanOwned: AugmentedError<ApiType>;73      /**74       * Destroying only empty collections is allowed75       **/76      CantDestroyNotEmptyCollection: AugmentedError<ApiType>;77      /**78       * Exceeded max admin count79       **/80      CollectionAdminCountExceeded: AugmentedError<ApiType>;81      /**82       * Collection description can not be longer than 255 char.83       **/84      CollectionDescriptionLimitExceeded: AugmentedError<ApiType>;85      /**86       * Tried to store more data than allowed in collection field87       **/88      CollectionFieldSizeExceeded: AugmentedError<ApiType>;89      /**90       * Tried to access an external collection with an internal API91       **/92      CollectionIsExternal: AugmentedError<ApiType>;93      /**94       * Tried to access an internal collection with an external API95       **/96      CollectionIsInternal: AugmentedError<ApiType>;97      /**98       * Collection limit bounds per collection exceeded99       **/100      CollectionLimitBoundsExceeded: AugmentedError<ApiType>;101      /**102       * Collection name can not be longer than 63 char.103       **/104      CollectionNameLimitExceeded: AugmentedError<ApiType>;105      /**106       * This collection does not exist.107       **/108      CollectionNotFound: AugmentedError<ApiType>;109      /**110       * Collection token limit exceeded111       **/112      CollectionTokenLimitExceeded: AugmentedError<ApiType>;113      /**114       * Token prefix can not be longer than 15 char.115       **/116      CollectionTokenPrefixLimitExceeded: AugmentedError<ApiType>;117      /**118       * Empty property keys are forbidden119       **/120      EmptyPropertyKey: AugmentedError<ApiType>;121      /**122       * Only ASCII letters, digits, and symbols `_`, `-`, and `.` are allowed123       **/124      InvalidCharacterInPropertyKey: AugmentedError<ApiType>;125      /**126       * Metadata flag frozen127       **/128      MetadataFlagFrozen: AugmentedError<ApiType>;129      /**130       * Sender parameter and item owner must be equal.131       **/132      MustBeTokenOwner: AugmentedError<ApiType>;133      /**134       * No permission to perform action135       **/136      NoPermission: AugmentedError<ApiType>;137      /**138       * Tried to store more property data than allowed139       **/140      NoSpaceForProperty: AugmentedError<ApiType>;141      /**142       * Insufficient funds to perform an action143       **/144      NotSufficientFounds: AugmentedError<ApiType>;145      /**146       * Tried to enable permissions which are only permitted to be disabled147       **/148      OwnerPermissionsCantBeReverted: AugmentedError<ApiType>;149      /**150       * Property key is too long151       **/152      PropertyKeyIsTooLong: AugmentedError<ApiType>;153      /**154       * Tried to store more property keys than allowed155       **/156      PropertyLimitReached: AugmentedError<ApiType>;157      /**158       * Collection is not in mint mode.159       **/160      PublicMintingNotAllowed: AugmentedError<ApiType>;161      /**162       * Only tokens from specific collections may nest tokens under this one163       **/164      SourceCollectionIsNotAllowedToNest: AugmentedError<ApiType>;165      /**166       * Item does not exist167       **/168      TokenNotFound: AugmentedError<ApiType>;169      /**170       * Item is balance not enough171       **/172      TokenValueTooLow: AugmentedError<ApiType>;173      /**174       * Total collections bound exceeded.175       **/176      TotalCollectionsLimitExceeded: AugmentedError<ApiType>;177      /**178       * Collection settings not allowing items transferring179       **/180      TransferNotAllowed: AugmentedError<ApiType>;181      /**182       * The operation is not supported183       **/184      UnsupportedOperation: AugmentedError<ApiType>;185      /**186       * User does not satisfy the nesting rule187       **/188      UserIsNotAllowedToNest: AugmentedError<ApiType>;189      /**190       * Generic error191       **/192      [key: string]: AugmentedError<ApiType>;193    };194    cumulusXcm: {195      /**196       * Generic error197       **/198      [key: string]: AugmentedError<ApiType>;199    };200    dmpQueue: {201      /**202       * The amount of weight given is possibly not enough for executing the message.203       **/204      OverLimit: AugmentedError<ApiType>;205      /**206       * The message index given is unknown.207       **/208      Unknown: AugmentedError<ApiType>;209      /**210       * Generic error211       **/212      [key: string]: AugmentedError<ApiType>;213    };214    ethereum: {215      /**216       * Signature is invalid.217       **/218      InvalidSignature: AugmentedError<ApiType>;219      /**220       * Pre-log is present, therefore transact is not allowed.221       **/222      PreLogExists: AugmentedError<ApiType>;223      /**224       * Generic error225       **/226      [key: string]: AugmentedError<ApiType>;227    };228    evm: {229      /**230       * Not enough balance to perform action231       **/232      BalanceLow: AugmentedError<ApiType>;233      /**234       * Calculating total fee overflowed235       **/236      FeeOverflow: AugmentedError<ApiType>;237      /**238       * Gas price is too low.239       **/240      GasPriceTooLow: AugmentedError<ApiType>;241      /**242       * Nonce is invalid243       **/244      InvalidNonce: AugmentedError<ApiType>;245      /**246       * Calculating total payment overflowed247       **/248      PaymentOverflow: AugmentedError<ApiType>;249      /**250       * Withdraw fee failed251       **/252      WithdrawFailed: AugmentedError<ApiType>;253      /**254       * Generic error255       **/256      [key: string]: AugmentedError<ApiType>;257    };258    evmCoderSubstrate: {259      OutOfFund: AugmentedError<ApiType>;260      OutOfGas: AugmentedError<ApiType>;261      /**262       * Generic error263       **/264      [key: string]: AugmentedError<ApiType>;265    };266    evmContractHelpers: {267      /**268       * No pending sponsor for contract.269       **/270      NoPendingSponsor: AugmentedError<ApiType>;271      /**272       * This method is only executable by owner.273       **/274      NoPermission: AugmentedError<ApiType>;275      /**276       * Generic error277       **/278      [key: string]: AugmentedError<ApiType>;279    };280    evmMigration: {281      AccountIsNotMigrating: AugmentedError<ApiType>;282      AccountNotEmpty: AugmentedError<ApiType>;283      /**284       * Generic error285       **/286      [key: string]: AugmentedError<ApiType>;287    };288    fungible: {289      /**290       * Fungible token does not support nesting.291       **/292      FungibleDisallowsNesting: AugmentedError<ApiType>;293      /**294       * Tried to set data for fungible item.295       **/296      FungibleItemsDontHaveData: AugmentedError<ApiType>;297      /**298       * Fungible tokens hold no ID, and the default value of TokenId for Fungible collection is 0.299       **/300      FungibleItemsHaveNoId: AugmentedError<ApiType>;301      /**302       * Not Fungible item data used to mint in Fungible collection.303       **/304      NotFungibleDataUsedToMintFungibleCollectionToken: AugmentedError<ApiType>;305      /**306       * Setting item properties is not allowed.307       **/308      SettingPropertiesNotAllowed: AugmentedError<ApiType>;309      /**310       * Generic error311       **/312      [key: string]: AugmentedError<ApiType>;313    };314    nonfungible: {315      /**316       * Unable to burn NFT with children317       **/318      CantBurnNftWithChildren: AugmentedError<ApiType>;319      /**320       * Used amount > 1 with NFT321       **/322      NonfungibleItemsHaveNoAmount: AugmentedError<ApiType>;323      /**324       * Not Nonfungible item data used to mint in Nonfungible collection.325       **/326      NotNonfungibleDataUsedToMintFungibleCollectionToken: AugmentedError<ApiType>;327      /**328       * Generic error329       **/330      [key: string]: AugmentedError<ApiType>;331    };332    parachainSystem: {333      /**334       * The inherent which supplies the host configuration did not run this block335       **/336      HostConfigurationNotAvailable: AugmentedError<ApiType>;337      /**338       * No code upgrade has been authorized.339       **/340      NothingAuthorized: AugmentedError<ApiType>;341      /**342       * No validation function upgrade is currently scheduled.343       **/344      NotScheduled: AugmentedError<ApiType>;345      /**346       * Attempt to upgrade validation function while existing upgrade pending347       **/348      OverlappingUpgrades: AugmentedError<ApiType>;349      /**350       * Polkadot currently prohibits this parachain from upgrading its validation function351       **/352      ProhibitedByPolkadot: AugmentedError<ApiType>;353      /**354       * The supplied validation function has compiled into a blob larger than Polkadot is355       * willing to run356       **/357      TooBig: AugmentedError<ApiType>;358      /**359       * The given code upgrade has not been authorized.360       **/361      Unauthorized: AugmentedError<ApiType>;362      /**363       * The inherent which supplies the validation data did not run this block364       **/365      ValidationDataNotAvailable: AugmentedError<ApiType>;366      /**367       * Generic error368       **/369      [key: string]: AugmentedError<ApiType>;370    };371    polkadotXcm: {372      /**373       * The location is invalid since it already has a subscription from us.374       **/375      AlreadySubscribed: AugmentedError<ApiType>;376      /**377       * The given location could not be used (e.g. because it cannot be expressed in the378       * desired version of XCM).379       **/380      BadLocation: AugmentedError<ApiType>;381      /**382       * The version of the `Versioned` value used is not able to be interpreted.383       **/384      BadVersion: AugmentedError<ApiType>;385      /**386       * Could not re-anchor the assets to declare the fees for the destination chain.387       **/388      CannotReanchor: AugmentedError<ApiType>;389      /**390       * The destination `MultiLocation` provided cannot be inverted.391       **/392      DestinationNotInvertible: AugmentedError<ApiType>;393      /**394       * The assets to be sent are empty.395       **/396      Empty: AugmentedError<ApiType>;397      /**398       * The message execution fails the filter.399       **/400      Filtered: AugmentedError<ApiType>;401      /**402       * Origin is invalid for sending.403       **/404      InvalidOrigin: AugmentedError<ApiType>;405      /**406       * The referenced subscription could not be found.407       **/408      NoSubscription: AugmentedError<ApiType>;409      /**410       * There was some other issue (i.e. not to do with routing) in sending the message. Perhaps411       * a lack of space for buffering the message.412       **/413      SendFailure: AugmentedError<ApiType>;414      /**415       * Too many assets have been attempted for transfer.416       **/417      TooManyAssets: AugmentedError<ApiType>;418      /**419       * The desired destination was unreachable, generally because there is a no way of routing420       * to it.421       **/422      Unreachable: AugmentedError<ApiType>;423      /**424       * The message's weight could not be determined.425       **/426      UnweighableMessage: AugmentedError<ApiType>;427      /**428       * Generic error429       **/430      [key: string]: AugmentedError<ApiType>;431    };432    promotion: {433      AdminNotSet: AugmentedError<ApiType>;434      AlreadySponsored: AugmentedError<ApiType>;435      InvalidArgument: AugmentedError<ApiType>;436      /**437       * No permission to perform action438       **/439      NoPermission: AugmentedError<ApiType>;440      /**441       * Insufficient funds to perform an action442       **/443      NotSufficientFounds: AugmentedError<ApiType>;444      /**445       * Generic error446       **/447      [key: string]: AugmentedError<ApiType>;448    };449    refungible: {450      /**451       * Not Refungible item data used to mint in Refungible collection.452       **/453      NotRefungibleDataUsedToMintFungibleCollectionToken: AugmentedError<ApiType>;454      /**455       * Refungible token can't nest other tokens.456       **/457      RefungibleDisallowsNesting: AugmentedError<ApiType>;458      /**459       * Refungible token can't be repartitioned by user who isn't owns all pieces.460       **/461      RepartitionWhileNotOwningAllPieces: AugmentedError<ApiType>;462      /**463       * Setting item properties is not allowed.464       **/465      SettingPropertiesNotAllowed: AugmentedError<ApiType>;466      /**467       * Maximum refungibility exceeded.468       **/469      WrongRefungiblePieces: AugmentedError<ApiType>;470      /**471       * Generic error472       **/473      [key: string]: AugmentedError<ApiType>;474    };475    rmrkCore: {476      /**477       * Not the target owner of the sent NFT.478       **/479      CannotAcceptNonOwnedNft: AugmentedError<ApiType>;480      /**481       * Not the target owner of the sent NFT.482       **/483      CannotRejectNonOwnedNft: AugmentedError<ApiType>;484      /**485       * NFT was not sent and is not pending.486       **/487      CannotRejectNonPendingNft: AugmentedError<ApiType>;488      /**489       * If an NFT is sent to a descendant, that would form a nesting loop, an ouroboros.490       * Sending to self is redundant.491       **/492      CannotSendToDescendentOrSelf: AugmentedError<ApiType>;493      /**494       * Too many tokens created in the collection, no new ones are allowed.495       **/496      CollectionFullOrLocked: AugmentedError<ApiType>;497      /**498       * Only destroying collections without tokens is allowed.499       **/500      CollectionNotEmpty: AugmentedError<ApiType>;501      /**502       * Collection does not exist, has a wrong type, or does not map to a Unique ID.503       **/504      CollectionUnknown: AugmentedError<ApiType>;505      /**506       * Property of the type of RMRK collection could not be read successfully.507       **/508      CorruptedCollectionType: AugmentedError<ApiType>;509      /**510       * Could not find an ID for a collection. It is likely there were too many collections created on the chain, causing an overflow.511       **/512      NoAvailableCollectionId: AugmentedError<ApiType>;513      /**514       * Token does not exist, or there is no suitable ID for it, likely too many tokens were created in a collection, causing an overflow.515       **/516      NoAvailableNftId: AugmentedError<ApiType>;517      /**518       * Could not find an ID for the resource. It is likely there were too many resources created on an NFT, causing an overflow.519       **/520      NoAvailableResourceId: AugmentedError<ApiType>;521      /**522       * Token is marked as non-transferable, and thus cannot be transferred.523       **/524      NonTransferable: AugmentedError<ApiType>;525      /**526       * No permission to perform action.527       **/528      NoPermission: AugmentedError<ApiType>;529      /**530       * No such resource found.531       **/532      ResourceDoesntExist: AugmentedError<ApiType>;533      /**534       * Resource is not pending for the operation.535       **/536      ResourceNotPending: AugmentedError<ApiType>;537      /**538       * Could not find a property by the supplied key.539       **/540      RmrkPropertyIsNotFound: AugmentedError<ApiType>;541      /**542       * Too many symbols supplied as the property key. The maximum is [256](up_data_structs::MAX_PROPERTY_KEY_LENGTH).543       **/544      RmrkPropertyKeyIsTooLong: AugmentedError<ApiType>;545      /**546       * Too many bytes supplied as the property value. The maximum is [32768](up_data_structs::MAX_PROPERTY_VALUE_LENGTH).547       **/548      RmrkPropertyValueIsTooLong: AugmentedError<ApiType>;549      /**550       * Something went wrong when decoding encoded data from the storage.551       * Perhaps, there was a wrong key supplied for the type, or the data was improperly stored.552       **/553      UnableToDecodeRmrkData: AugmentedError<ApiType>;554      /**555       * Generic error556       **/557      [key: string]: AugmentedError<ApiType>;558    };559    rmrkEquip: {560      /**561       * Base collection linked to this ID does not exist.562       **/563      BaseDoesntExist: AugmentedError<ApiType>;564      /**565       * No Theme named "default" is associated with the Base.566       **/567      NeedsDefaultThemeFirst: AugmentedError<ApiType>;568      /**569       * Could not find an ID for a Base collection. It is likely there were too many collections created on the chain, causing an overflow.570       **/571      NoAvailableBaseId: AugmentedError<ApiType>;572      /**573       * Could not find a suitable ID for a Part, likely too many Part tokens were created in the Base, causing an overflow574       **/575      NoAvailablePartId: AugmentedError<ApiType>;576      /**577       * Cannot assign equippables to a fixed Part.578       **/579      NoEquippableOnFixedPart: AugmentedError<ApiType>;580      /**581       * Part linked to this ID does not exist.582       **/583      PartDoesntExist: AugmentedError<ApiType>;584      /**585       * No permission to perform action.586       **/587      PermissionError: AugmentedError<ApiType>;588      /**589       * Generic error590       **/591      [key: string]: AugmentedError<ApiType>;592    };593    scheduler: {594      /**595       * Failed to schedule a call596       **/597      FailedToSchedule: AugmentedError<ApiType>;598      /**599       * Cannot find the scheduled call.600       **/601      NotFound: AugmentedError<ApiType>;602      /**603       * Reschedule failed because it does not change scheduled time.604       **/605      RescheduleNoChange: AugmentedError<ApiType>;606      /**607       * Given target block number is in the past.608       **/609      TargetBlockNumberInPast: AugmentedError<ApiType>;610      /**611       * Generic error612       **/613      [key: string]: AugmentedError<ApiType>;614    };615    structure: {616      /**617       * While nesting, reached the breadth limit of nesting, exceeding the provided budget.618       **/619      BreadthLimit: AugmentedError<ApiType>;620      /**621       * While nesting, reached the depth limit of nesting, exceeding the provided budget.622       **/623      DepthLimit: AugmentedError<ApiType>;624      /**625       * While nesting, encountered an already checked account, detecting a loop.626       **/627      OuroborosDetected: AugmentedError<ApiType>;628      /**629       * Couldn't find the token owner that is itself a token.630       **/631      TokenNotFound: AugmentedError<ApiType>;632      /**633       * Generic error634       **/635      [key: string]: AugmentedError<ApiType>;636    };637    sudo: {638      /**639       * Sender must be the Sudo account640       **/641      RequireSudo: AugmentedError<ApiType>;642      /**643       * Generic error644       **/645      [key: string]: AugmentedError<ApiType>;646    };647    system: {648      /**649       * The origin filter prevent the call to be dispatched.650       **/651      CallFiltered: AugmentedError<ApiType>;652      /**653       * Failed to extract the runtime version from the new runtime.654       * 655       * Either calling `Core_version` or decoding `RuntimeVersion` failed.656       **/657      FailedToExtractRuntimeVersion: AugmentedError<ApiType>;658      /**659       * The name of specification does not match between the current runtime660       * and the new runtime.661       **/662      InvalidSpecName: AugmentedError<ApiType>;663      /**664       * Suicide called when the account has non-default composite data.665       **/666      NonDefaultComposite: AugmentedError<ApiType>;667      /**668       * There is a non-zero reference count preventing the account from being purged.669       **/670      NonZeroRefCount: AugmentedError<ApiType>;671      /**672       * The specification version is not allowed to decrease between the current runtime673       * and the new runtime.674       **/675      SpecVersionNeedsToIncrease: AugmentedError<ApiType>;676      /**677       * Generic error678       **/679      [key: string]: AugmentedError<ApiType>;680    };681    treasury: {682      /**683       * The spend origin is valid but the amount it is allowed to spend is lower than the684       * amount to be spent.685       **/686      InsufficientPermission: AugmentedError<ApiType>;687      /**688       * Proposer's balance is too low.689       **/690      InsufficientProposersBalance: AugmentedError<ApiType>;691      /**692       * No proposal or bounty at that index.693       **/694      InvalidIndex: AugmentedError<ApiType>;695      /**696       * Proposal has not been approved.697       **/698      ProposalNotApproved: AugmentedError<ApiType>;699      /**700       * Too many approvals in the queue.701       **/702      TooManyApprovals: AugmentedError<ApiType>;703      /**704       * Generic error705       **/706      [key: string]: AugmentedError<ApiType>;707    };708    unique: {709      /**710       * Decimal_points parameter must be lower than [`up_data_structs::MAX_DECIMAL_POINTS`].711       **/712      CollectionDecimalPointLimitExceeded: AugmentedError<ApiType>;713      /**714       * This address is not set as sponsor, use setCollectionSponsor first.715       **/716      ConfirmUnsetSponsorFail: AugmentedError<ApiType>;717      /**718       * Length of items properties must be greater than 0.719       **/720      EmptyArgument: AugmentedError<ApiType>;721      /**722       * Repertition is only supported by refungible collection.723       **/724      RepartitionCalledOnNonRefungibleCollection: AugmentedError<ApiType>;725      /**726       * Generic error727       **/728      [key: string]: AugmentedError<ApiType>;729    };730    vesting: {731      /**732       * The vested transfer amount is too low733       **/734      AmountLow: AugmentedError<ApiType>;735      /**736       * Insufficient amount of balance to lock737       **/738      InsufficientBalanceToLock: AugmentedError<ApiType>;739      /**740       * Failed because the maximum vesting schedules was exceeded741       **/742      MaxVestingSchedulesExceeded: AugmentedError<ApiType>;743      /**744       * This account have too many vesting schedules745       **/746      TooManyVestingSchedules: AugmentedError<ApiType>;747      /**748       * Vesting period is zero749       **/750      ZeroVestingPeriod: AugmentedError<ApiType>;751      /**752       * Number of vests is zero753       **/754      ZeroVestingPeriodCount: AugmentedError<ApiType>;755      /**756       * Generic error757       **/758      [key: string]: AugmentedError<ApiType>;759    };760    xcmpQueue: {761      /**762       * Bad overweight index.763       **/764      BadOverweightIndex: AugmentedError<ApiType>;765      /**766       * Bad XCM data.767       **/768      BadXcm: AugmentedError<ApiType>;769      /**770       * Bad XCM origin.771       **/772      BadXcmOrigin: AugmentedError<ApiType>;773      /**774       * Failed to send XCM message.775       **/776      FailedToSend: AugmentedError<ApiType>;777      /**778       * Provided weight is possibly not enough to execute the message.779       **/780      WeightOverLimit: AugmentedError<ApiType>;781      /**782       * Generic error783       **/784      [key: string]: AugmentedError<ApiType>;785    };786  } // AugmentedErrors787} // declare module
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -525,7 +525,7 @@
        **/
       staked: AugmentedQuery<ApiType, (arg1: AccountId32 | string | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<u128>, [AccountId32, u32]> & QueryableStorageEntry<ApiType, [AccountId32, u32]>;
       /**
-       * A block when app-promotion has started
+       * A block when app-promotion has started .I think this is redundant, because we only need `NextInterestBlock`.
        **/
       startBlock: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
       totalStaked: AugmentedQuery<ApiType, () => Observable<u128>, []> & QueryableStorageEntry<ApiType, []>;
modifiedtests/src/interfaces/augment-api-tx.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-tx.ts
+++ b/tests/src/interfaces/augment-api-tx.ts
@@ -365,9 +365,12 @@
     promotion: {
       setAdminAddress: AugmentedSubmittable<(admin: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [PalletEvmAccountBasicCrossAccountIdRepr]>;
       sponsorCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      sponsorConract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
       stake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
       startAppPromotion: AugmentedSubmittable<(promotionStartRelayBlock: Option<u32> | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic<ApiType>, [Option<u32>]>;
+      stopAppPromotion: AugmentedSubmittable<() => SubmittableExtrinsic<ApiType>, []>;
       stopSponsorignCollection: AugmentedSubmittable<(collectionId: u32 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u32]>;
+      stopSponsorignContract: AugmentedSubmittable<(contractId: H160 | string | Uint8Array) => SubmittableExtrinsic<ApiType>, [H160]>;
       unstake: AugmentedSubmittable<(amount: u128 | AnyNumber | Uint8Array) => SubmittableExtrinsic<ApiType>, [u128]>;
       /**
        * Generic tx
modifiedtests/src/interfaces/default/types.tsdiffbeforeafterboth
--- a/tests/src/interfaces/default/types.ts
+++ b/tests/src/interfaces/default/types.ts
@@ -816,6 +816,7 @@
   readonly asStartAppPromotion: {
     readonly promotionStartRelayBlock: Option<u32>;
   } & Struct;
+  readonly isStopAppPromotion: boolean;
   readonly isStake: boolean;
   readonly asStake: {
     readonly amount: u128;
@@ -832,7 +833,15 @@
   readonly asStopSponsorignCollection: {
     readonly collectionId: u32;
   } & Struct;
-  readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+  readonly isSponsorConract: boolean;
+  readonly asSponsorConract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly isStopSponsorignContract: boolean;
+  readonly asStopSponsorignContract: {
+    readonly contractId: H160;
+  } & Struct;
+  readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
 }
 
 /** @name PalletAppPromotionError */
@@ -841,8 +850,7 @@
   readonly isNoPermission: boolean;
   readonly isNotSufficientFounds: boolean;
   readonly isInvalidArgument: boolean;
-  readonly isAlreadySponsored: boolean;
-  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+  readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
 }
 
 /** @name PalletAppPromotionEvent */
modifiedtests/src/interfaces/lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/lookup.ts
+++ b/tests/src/interfaces/lookup.ts
@@ -2463,6 +2463,7 @@
       start_app_promotion: {
         promotionStartRelayBlock: 'Option<u32>',
       },
+      stop_app_promotion: 'Null',
       stake: {
         amount: 'u128',
       },
@@ -2473,7 +2474,13 @@
         collectionId: 'u32',
       },
       stop_sponsorign_collection: {
-        collectionId: 'u32'
+        collectionId: 'u32',
+      },
+      sponsor_conract: {
+        contractId: 'H160',
+      },
+      stop_sponsorign_contract: {
+        contractId: 'H160'
       }
     }
   },
@@ -3098,7 +3105,7 @@
    * Lookup410: pallet_app_promotion::pallet::Error<T>
    **/
   PalletAppPromotionError: {
-    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument', 'AlreadySponsored']
+    _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFounds', 'InvalidArgument']
   },
   /**
    * Lookup413: pallet_evm::pallet::Error<T>
modifiedtests/src/interfaces/types-lookup.tsdiffbeforeafterboth
--- a/tests/src/interfaces/types-lookup.ts
+++ b/tests/src/interfaces/types-lookup.ts
@@ -2669,6 +2669,7 @@
     readonly asStartAppPromotion: {
       readonly promotionStartRelayBlock: Option<u32>;
     } & Struct;
+    readonly isStopAppPromotion: boolean;
     readonly isStake: boolean;
     readonly asStake: {
       readonly amount: u128;
@@ -2685,7 +2686,15 @@
     readonly asStopSponsorignCollection: {
       readonly collectionId: u32;
     } & Struct;
-    readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection';
+    readonly isSponsorConract: boolean;
+    readonly asSponsorConract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly isStopSponsorignContract: boolean;
+    readonly asStopSponsorignContract: {
+      readonly contractId: H160;
+    } & Struct;
+    readonly type: 'SetAdminAddress' | 'StartAppPromotion' | 'StopAppPromotion' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsorignCollection' | 'SponsorConract' | 'StopSponsorignContract';
   }
 
   /** @name PalletEvmCall (305) */
@@ -3288,8 +3297,7 @@
     readonly isNoPermission: boolean;
     readonly isNotSufficientFounds: boolean;
     readonly isInvalidArgument: boolean;
-    readonly isAlreadySponsored: boolean;
-    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument' | 'AlreadySponsored';
+    readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFounds' | 'InvalidArgument';
   }
 
   /** @name PalletEvmError (413) */