From 8c41979584a9c3128722359cfb4e674be351e6a7 Mon Sep 17 00:00:00 2001 From: f-gate Date: Tue, 21 Nov 2023 08:54:35 +0000 Subject: [PATCH 1/6] staging check for prs to main --- .github/workflows/build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 039e8965..236d973b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,11 +4,13 @@ on: push: branches: - main + - staging paths-ignore: - "**.md" pull_request: branches: - main + - staging paths-ignore: - "**.md" env: @@ -16,6 +18,15 @@ env: GCP_ZONE: europe-west3-a jobs: + check_branch: + runs-on: ubuntu-latest + steps: + - name: Check branch + if: github.base_ref == 'main' && github.head_ref != 'staging' + run: | + echo "ERROR: You can only merge to main from staging." + exit 1 + create-runner: runs-on: ubuntu-latest outputs: From fff83cdedceb674b2f373c426bfd3474459a2dd9 Mon Sep 17 00:00:00 2001 From: f-gate Date: Tue, 21 Nov 2023 09:07:07 +0000 Subject: [PATCH 2/6] remove run gcp for push to staging --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 236d973b..2820bab2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,7 +4,6 @@ on: push: branches: - main - - staging paths-ignore: - "**.md" pull_request: From eb0f559d8678efd820bebee63c0bf0d31d7501b5 Mon Sep 17 00:00:00 2001 From: gatsey <42411328+f-gate@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:22:56 +0000 Subject: [PATCH 3/6] Try runtime fixes (#281) * fix try-runtime * try-runtime ci * fix * fix * fix * fmt * take out ci into new branch * bump spec * ci * fix --- .github/workflows/build.yml | 15 ++++-------- .github/workflows/fmt.yml | 37 +++++++++++++++++++++++++++++ pallets/fellowship/src/migration.rs | 1 + pallets/proposals/src/lib.rs | 2 +- pallets/proposals/src/migration.rs | 16 +++++++++---- runtime/imbue-kusama/src/lib.rs | 7 +++--- 6 files changed, 59 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/fmt.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 039e8965..d903efe7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: build-on-self-hosted-runner +name: test-features on: workflow_dispatch: push: @@ -35,9 +35,9 @@ jobs: ephemeral: true test-features: - needs: create-runner - runs-on: ${{ needs.create-runner.outputs.label }} - steps: + needs: create-runner + runs-on: ${{ needs.create-runner.outputs.label }} + steps: - uses: actions/checkout@v4 - name: Set HOME @@ -50,16 +50,9 @@ jobs: toolchain: nightly-2023-05-22 target: wasm32-unknown-unknown override: true - components: rustfmt, clippy - name: Install Dependencies run: sudo apt install protobuf-compiler clang build-essential -y - - name: 🫠 rustfmt 🫠 - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --check - - name: Run tests with benchmarks run: cargo test --features runtime-benchmarks \ No newline at end of file diff --git a/.github/workflows/fmt.yml b/.github/workflows/fmt.yml new file mode 100644 index 00000000..7c7054f7 --- /dev/null +++ b/.github/workflows/fmt.yml @@ -0,0 +1,37 @@ +name: cargo-fmt +on: + workflow_dispatch: + push: + branches: + - main + paths-ignore: + - "**.md" + pull_request: + branches: + - main + paths-ignore: + - "**.md" + +jobs: + cargo-fmt: + runs-on: ${{ needs.create-runner.outputs.label }} + steps: + - uses: actions/checkout@v4 + + - name: Set HOME + run: echo "HOME=/home/ubuntu" >> ${GITHUB_ENV} + + - name: Install minimal nightly Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2023-05-22 + target: wasm32-unknown-unknown + override: true + components: rustfmt, clippy + + - name: 🫠 rustfmt 🫠 + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --check \ No newline at end of file diff --git a/pallets/fellowship/src/migration.rs b/pallets/fellowship/src/migration.rs index 10fcc478..5b725130 100644 --- a/pallets/fellowship/src/migration.rs +++ b/pallets/fellowship/src/migration.rs @@ -103,6 +103,7 @@ pub mod v0 { weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 1)); } else { log::warn!("Skipping v1, should be removed from Executive"); + log::warn!("on_chain = {:?}, current = {:?}", onchain, current); weight = weight.saturating_add(T::DbWeight::get().reads(1)); } diff --git a/pallets/proposals/src/lib.rs b/pallets/proposals/src/lib.rs index 3d25cb29..057e3f01 100644 --- a/pallets/proposals/src/lib.rs +++ b/pallets/proposals/src/lib.rs @@ -132,7 +132,7 @@ pub mod pallet { type AssetSignerOrigin: EnsureOrigin; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/pallets/proposals/src/migration.rs b/pallets/proposals/src/migration.rs index 1e04bb71..ce142def 100644 --- a/pallets/proposals/src/migration.rs +++ b/pallets/proposals/src/migration.rs @@ -736,7 +736,8 @@ pub mod v7 { let onchain = as GetStorageVersion>::on_chain_storage_version(); ensure!( - ::MaxJuryMembers::get() < u8::MAX as u32, + <::JurySelector as SelectJury>>::JurySize::get() + < u8::MAX as u32, "Max jury members must be smaller than u8" ); @@ -770,6 +771,9 @@ pub mod v7 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { log::warn!( target: "pallet-proposals", "Running post_upgrade()"); + Projects::::iter().for_each(|(_k, project)| { + assert!(project.jury.len() > 0, "jury size must be > 0"); + }); ensure!( Pallet::::current_storage_version() == 7, @@ -793,8 +797,6 @@ pub mod v7 { v5::FundingType::Grant(_) => crate::FundingPath::WaitForFunding, }; - let jury = >>::select_jury(); - let refund_locations: BoundedVec<(Locality>, Percent), T::MaximumContributorsPerProject> = match project.funding_type { v5::FundingType::Proposal => crate::Pallet::::convert_contributions_to_refund_locations(&project.contributions), v5::FundingType::Brief => crate::Pallet::::convert_contributions_to_refund_locations(&project.contributions), @@ -812,6 +814,12 @@ pub mod v7 { }, }; + let jury = match project.funding_type { + v5::FundingType::Grant(_) => project.contributions.keys().cloned().collect::>>(), + v5::FundingType::Brief => >>::select_jury().to_vec(), + _ => >>::select_jury().to_vec(), + }; + let mut new_milestones: BoundedBTreeMilestones = BoundedBTreeMap::new(); project.milestones.iter().for_each(|(_ms_key, ms): (&MilestoneKey, &v6::V6Milestone)| { // assume that if its approved then its been withdrawn. @@ -844,7 +852,7 @@ pub mod v7 { cancelled: project.cancelled, deposit_id: project.deposit_id, refund_locations, - jury, + jury: jury.try_into().expect("contributions bound is larger than jury bound, reduce contribution bound or increase jury bound."), on_creation_funding, refunded_funds: Zero::zero(), }; diff --git a/runtime/imbue-kusama/src/lib.rs b/runtime/imbue-kusama/src/lib.rs index 7d202698..0b7e9425 100644 --- a/runtime/imbue-kusama/src/lib.rs +++ b/runtime/imbue-kusama/src/lib.rs @@ -101,7 +101,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("imbue"), impl_name: create_runtime_str!("imbue"), authoring_version: 2, - spec_version: 1_000_000, + spec_version: 1_000_001, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 2, @@ -181,12 +181,13 @@ pub mod migrations { use super::*; /// Unreleased migrations. Add new ones here: pub type Unreleased = ( - pallet_proposals::migration::v7::MigrateToV7, + pallet_fellowship::migration::v0::MigrateInitial, pallet_balances::migration::MigrateToTrackInactive, pallet_collator_selection::migration::v1::MigrateToV1, pallet_xcm::migration::v1::VersionUncheckedMigrateToV1, - pallet_fellowship::migration::v0::MigrateInitial, orml_unknown_tokens::Migration, + // PROPOSALS MIGRATION MUST BE RUN AFTER FELLOWSHIP MIGRATION + pallet_proposals::migration::v7::MigrateToV7, ); } From b232698bdc3076bad7f06a6ca60afd6164e5b8d9 Mon Sep 17 00:00:00 2001 From: gatsey <42411328+f-gate@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:45:27 +0000 Subject: [PATCH 4/6] Create a staging branch (#272) * staging check for prs to main * remove run gcp for push to staging --- .github/workflows/build.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d903efe7..58b35af0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,7 @@ on: pull_request: branches: - main + - staging paths-ignore: - "**.md" env: @@ -16,6 +17,15 @@ env: GCP_ZONE: europe-west3-a jobs: + check_branch: + runs-on: ubuntu-latest + steps: + - name: Check branch + if: github.base_ref == 'main' && github.head_ref != 'staging' + run: | + echo "ERROR: You can only merge to main from staging." + exit 1 + create-runner: runs-on: ubuntu-latest outputs: From 210ccc369cd1ce5972fa42618d8e00768b6e439e Mon Sep 17 00:00:00 2001 From: gatsey <42411328+f-gate@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:55:50 +0000 Subject: [PATCH 5/6] Optional fellowship switch (#280) * add test for approved applicant * fmt * add ensure role to runtime --------- Co-authored-by: Sam Elamin --- pallets/briefs/src/benchmarking.rs | 10 +++-- pallets/briefs/src/integration_tests.rs | 1 + pallets/briefs/src/lib.rs | 9 +++++ pallets/briefs/src/mock.rs | 30 +++++++++++++- pallets/briefs/src/tests.rs | 53 ++++++++++++++++++++++++- pallets/fellowship/src/impls.rs | 2 +- pallets/fellowship/src/traits.rs | 4 +- runtime/imbue-kusama/src/lib.rs | 1 + 8 files changed, 101 insertions(+), 9 deletions(-) diff --git a/pallets/briefs/src/benchmarking.rs b/pallets/briefs/src/benchmarking.rs index 250f1889..738e3810 100644 --- a/pallets/briefs/src/benchmarking.rs +++ b/pallets/briefs/src/benchmarking.rs @@ -41,6 +41,7 @@ mod benchmarks { brief_id, CurrencyId::Native, milestones, + false, ); assert_last_event::(Event::::BriefSubmitted(caller, brief_id).into()); } @@ -63,7 +64,8 @@ mod benchmarks { initial_contribution, brief_id, CurrencyId::Native, - milestones + milestones, + false, )); let brief_owner: T::AccountId = brief_owners[0].clone(); // (brief_owner, brief_id, contribution) @@ -93,7 +95,8 @@ mod benchmarks { initial_contribution, brief_id, CurrencyId::Native, - milestones + milestones, + false, )); // (origin, brief_id) #[extrinsic_call] @@ -118,7 +121,8 @@ mod benchmarks { initial_contribution, brief_id, CurrencyId::Native, - milestones + milestones, + false, )); // (origin, brief_id) #[extrinsic_call] diff --git a/pallets/briefs/src/integration_tests.rs b/pallets/briefs/src/integration_tests.rs index 02073748..798c2e55 100644 --- a/pallets/briefs/src/integration_tests.rs +++ b/pallets/briefs/src/integration_tests.rs @@ -22,6 +22,7 @@ fn create_proposal_from_brief() { brief_id, CurrencyId::Native, get_milestones(10), + false, ); assert_ok!(BriefsMod::commence_work( diff --git a/pallets/briefs/src/lib.rs b/pallets/briefs/src/lib.rs index 88588a5f..0527f536 100644 --- a/pallets/briefs/src/lib.rs +++ b/pallets/briefs/src/lib.rs @@ -31,6 +31,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; use orml_traits::{MultiCurrency, MultiReservableCurrency}; use pallet_deposits::traits::DepositHandler; + use pallet_fellowship::traits::EnsureRole; use pallet_fellowship::traits::SelectJury; use pallet_proposals::traits::IntoProposal; use pallet_proposals::{Contribution, FundingPath, ProposedMilestone}; @@ -82,9 +83,12 @@ pub mod pallet { type MaxMilestonesPerBrief: Get; /// Storage deposits. type BriefStorageItem: Get>; + /// Handler for deposits. type DepositHandler: DepositHandler, AccountIdOf>; /// The type that selects a list of jury members. type JurySelector: SelectJury>; + /// Type for ensuring an account is of a given fellowship role. + type EnsureRole: pallet_fellowship::traits::EnsureRole>; /// The weight info for the extrinsics. type WeightInfo: WeightInfoT; } @@ -170,9 +174,14 @@ pub mod pallet { brief_id: BriefHash, currency_id: CurrencyId, milestones: BoundedProposedMilestones, + require_fellowship: bool, ) -> DispatchResult { let who = ensure_signed(origin)?; + if require_fellowship { + T::EnsureRole::ensure_role(&applicant, pallet_fellowship::Role::Freelancer, None)?; + } + ensure!( Briefs::::get(brief_id).is_none(), Error::::BriefAlreadyExists diff --git a/pallets/briefs/src/mock.rs b/pallets/briefs/src/mock.rs index 2d67a24b..96a84f44 100644 --- a/pallets/briefs/src/mock.rs +++ b/pallets/briefs/src/mock.rs @@ -18,6 +18,8 @@ use sp_runtime::{ }; use pallet_deposits::traits::DepositHandler; +use pallet_fellowship::traits::FellowshipHandle; +use pallet_fellowship::Role; use sp_std::{ convert::{TryFrom, TryInto}, str, @@ -57,6 +59,7 @@ frame_support::construct_runtime!( BriefsMod: pallet_briefs::{Pallet, Call, Storage, Event}, Proposals: pallet_proposals::{Pallet, Call, Storage, Event}, Identity: pallet_identity::{Pallet, Call, Storage, Event}, + Fellowship: pallet_fellowship::{Pallet, Call, Storage, Event}, } ); @@ -209,6 +212,7 @@ impl pallet_briefs::Config for Test { type DepositHandler = MockDepositHandler; type WeightInfo = pallet_briefs::WeightInfo; type JurySelector = MockJurySelector; + type EnsureRole = pallet_fellowship::impls::EnsureFellowshipRole; } parameter_types! { @@ -273,13 +277,36 @@ impl pallet_identity::Config for Test { type WeightInfo = (); } +parameter_types! { + pub MaxCandidatesPerShortlist: u32 = 100; + pub ShortlistPeriod: BlockNumber = 100; + pub MembershipDeposit: Balance = 50_000_000; + pub SlashAccount: AccountId = 1; + pub DepositCurrencyId: CurrencyId = CurrencyId::Native; +} + +impl pallet_fellowship::Config for Test { + type RuntimeEvent = RuntimeEvent; + type MultiCurrency = Tokens; + type ForceAuthority = EnsureRoot; + type MaxCandidatesPerShortlist = MaxCandidatesPerShortlist; + type ShortlistPeriod = ShortlistPeriod; + type MembershipDeposit = MembershipDeposit; + type DepositCurrencyId = DepositCurrencyId; + type SlashAccount = SlashAccount; + type Permissions = pallet_fellowship::impls::VetterAndFreelancerAllPermissions; + type WeightInfo = pallet_fellowship::weights::WeightInfo; +} + parameter_types! { pub const UnitWeightCost: u64 = 10; pub const MaxInstructions: u32 = 100; } + pub static ALICE: AccountId = 125; pub static BOB: AccountId = 126; pub static CHARLIE: AccountId = 127; +pub static FREELANCER: AccountId = 1270; pub static TREASURY: AccountId = 200; pub(crate) fn build_test_externality() -> sp_io::TestExternalities { @@ -292,7 +319,7 @@ pub(crate) fn build_test_externality() -> sp_io::TestExternalities { .unwrap(); orml_tokens::GenesisConfig:: { balances: { - vec![ALICE, BOB, CHARLIE] + vec![ALICE, BOB, CHARLIE, FREELANCER] .into_iter() .map(|id| (id, CurrencyId::Native, 1000000)) .collect::>() @@ -303,6 +330,7 @@ pub(crate) fn build_test_externality() -> sp_io::TestExternalities { let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| { + pallet_fellowship::Roles::::insert(&FREELANCER, (Role::Freelancer, 10)); System::set_block_number(1); }); ext diff --git a/pallets/briefs/src/tests.rs b/pallets/briefs/src/tests.rs index 8470dfe3..09600553 100644 --- a/pallets/briefs/src/tests.rs +++ b/pallets/briefs/src/tests.rs @@ -6,16 +6,53 @@ use crate::*; use common_types::CurrencyId; use frame_support::{assert_noop, assert_ok, pallet_prelude::*}; use orml_traits::{MultiCurrency, MultiReservableCurrency}; +use pallet_fellowship::traits::EnsureRole; use pallet_proposals::{BoundedProposedMilestones, Projects, ProposedMilestone}; use sp_arithmetic::per_things::Percent; +use sp_runtime::DispatchError::BadOrigin; use std::convert::TryInto; #[test] fn create_brief_not_approved_applicant() { build_test_externality().execute_with(|| { - // TODO: - // Only accounts in the fellowship can apply for work + assert_noop!( + BriefsMod::create_brief( + RuntimeOrigin::signed(BOB), + get_brief_owners(u32::MAX), + ALICE, + 100000, + 10000, + gen_hash(1), + CurrencyId::Native, + get_milestones(10), + true, + ), + BadOrigin + ); + }); +} + +#[test] +fn create_brief_approved_applicant() { + build_test_externality().execute_with(|| { + assert_ok!(::EnsureRole::ensure_role( + &FREELANCER, + pallet_fellowship::Role::Freelancer, + None + )); + + assert_ok!(BriefsMod::create_brief( + RuntimeOrigin::signed(BOB), + get_brief_owners(10), + FREELANCER, + 100000, + 10000, + gen_hash(1), + CurrencyId::Native, + get_milestones(10), + true, + )); }); } @@ -32,6 +69,7 @@ fn create_brief_brief_owner_overflow() { gen_hash(1), CurrencyId::Native, get_milestones(10), + false, ), Error::::TooManyBriefOwners ); @@ -50,6 +88,7 @@ fn create_brief_with_no_contribution_ok() { gen_hash(1), CurrencyId::Native, get_milestones(10), + false, )); }); } @@ -70,6 +109,7 @@ fn create_brief_no_contribution_and_contribute() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); (0..5).for_each(|_| { @@ -111,6 +151,7 @@ fn contribute_to_brief_not_brief_owner() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_noop!( @@ -139,6 +180,7 @@ fn contribute_to_brief_more_than_total_ok() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_ok!(BriefsMod::contribute_to_brief( RuntimeOrigin::signed(BOB), @@ -163,6 +205,7 @@ fn create_brief_already_exists() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_noop!( @@ -175,6 +218,7 @@ fn create_brief_already_exists() { brief_id, CurrencyId::Native, get_milestones(10), + false, ), Error::::BriefAlreadyExists ); @@ -196,6 +240,7 @@ fn only_applicant_can_start_work() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_noop!( @@ -225,6 +270,7 @@ fn initial_contribution_and_extra_contribution_aggregates() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_ok!(BriefsMod::contribute_to_brief( @@ -261,6 +307,7 @@ fn reserved_funds_are_transferred_to_project_kitty() { brief_id, CurrencyId::Native, get_milestones(10), + false, ); assert_ok!(BriefsMod::commence_work( @@ -293,6 +340,7 @@ fn cancel_brief_works() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_ok!(BriefsMod::contribute_to_brief( @@ -359,6 +407,7 @@ fn cancel_brief_not_brief_owner() { brief_id, CurrencyId::Native, get_milestones(10), + false, )); assert_noop!( diff --git a/pallets/fellowship/src/impls.rs b/pallets/fellowship/src/impls.rs index b8b5d8fd..26637312 100644 --- a/pallets/fellowship/src/impls.rs +++ b/pallets/fellowship/src/impls.rs @@ -8,7 +8,7 @@ use sp_std::{vec, vec::Vec}; /// Ensure that a account is of a given role. /// Used in other pallets like an ensure origin. pub struct EnsureFellowshipRole(T); -impl EnsureRole, Role> for EnsureFellowshipRole { +impl EnsureRole> for EnsureFellowshipRole { type Success = (); fn ensure_role( diff --git a/pallets/fellowship/src/traits.rs b/pallets/fellowship/src/traits.rs index de1cb52a..69b19495 100644 --- a/pallets/fellowship/src/traits.rs +++ b/pallets/fellowship/src/traits.rs @@ -1,4 +1,4 @@ -use crate::Rank; +use crate::{Rank, Role}; use codec::{FullCodec, FullEncode}; use frame_support::{pallet_prelude::*, weights::Weight}; use sp_runtime::DispatchError; @@ -19,7 +19,7 @@ pub trait FellowshipHandle { fn revoke_fellowship(who: &AccountId, slash_deposit: bool) -> Result<(), DispatchError>; } -pub trait EnsureRole { +pub trait EnsureRole { type Success; fn ensure_role( acc: &AccountId, diff --git a/runtime/imbue-kusama/src/lib.rs b/runtime/imbue-kusama/src/lib.rs index 0b7e9425..29a4b1bb 100644 --- a/runtime/imbue-kusama/src/lib.rs +++ b/runtime/imbue-kusama/src/lib.rs @@ -868,6 +868,7 @@ impl pallet_briefs::Config for Runtime { type BriefStorageItem = BriefStorageItem; type DepositHandler = Deposits; type JurySelector = PointerBasedJurySelector; + type EnsureRole = pallet_fellowship::impls::EnsureFellowshipRole; } parameter_types! { From 42953521bac82b2690152855d5fbcfd2c613d203 Mon Sep 17 00:00:00 2001 From: gatsey <42411328+f-gate@users.noreply.github.com> Date: Tue, 5 Dec 2023 13:58:14 +0000 Subject: [PATCH 6/6] remove it (#285) --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58b35af0..bc29d304 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,6 @@ on: - "**.md" env: CARGO_TERM_COLOR: always - GCP_ZONE: europe-west3-a jobs: check_branch: @@ -41,7 +40,6 @@ jobs: image_family: ubuntu-2004-lts machine_type: e2-highcpu-32 disk_size: 100 - machine_zone: ${{ env.GCP_ZONE }} ephemeral: true test-features: