3

In trying to learning how governance works, I have implemented a bare-minimum governance configuration. I run it on a local relay chain and passed referenda always fail enactment with BadOrigin no matter what extrinsic is proposed. Even a simple balance transfer initiated by the origin account for the transfer fails. All proposals are proposed by a Council member, using the Polkadot UI -> Democracy tab. Everything else works as expected. Any ideas why this could be happening?

Here is my governance-related config:

parameter_types! {
    pub MaximumSchedulerWeight: Weight = Perbill::from_percent(10) * RuntimeBlockWeights::get().max_block;
    pub const MaxScheduledPerBlock: u32 = 50;
    pub const NoPreimagePostponement: Option<u32> = Some(5 * MINUTES);
}

impl pallet_scheduler::Config for Runtime {
    type Event = Event;
    type Origin = Origin;
    type PalletsOrigin = OriginCaller;
    type Call = Call;
    type MaximumWeight = MaximumSchedulerWeight;
    type ScheduleOrigin = EnsureRoot<AccountId>;
    type MaxScheduledPerBlock = MaxScheduledPerBlock;
    type WeightInfo = pallet_scheduler::weights::SubstrateWeight<Runtime>;
    type OriginPrivilegeCmp = EqualPrivilegeOnly;
    type PreimageProvider = Preimage;
    type NoPreimagePostponement = NoPreimagePostponement;
}

parameter_types! {
    pub const PreimageMaxSize: u32 = 4096 * 1024;
    pub const PreimageBaseDeposit: Balance = 1 * MILLIUNIT;
    pub const PreimageByteDeposit: Balance = 1 * MICROUNIT;
}

impl pallet_preimage::Config for Runtime {
    type WeightInfo = pallet_preimage::weights::SubstrateWeight<Runtime>;
    type Event = Event;
    type Currency = Balances;
    type ManagerOrigin = EnsureRoot<AccountId>;
    type MaxSize = PreimageMaxSize;
    type BaseDeposit = PreimageBaseDeposit;
    type ByteDeposit = PreimageByteDeposit;
}

parameter_types! {
    pub const CouncilMotionDuration: BlockNumber = 5 * DAYS;
    pub const CouncilMaxProposals: u32 = 25;
    pub const CouncilMaxMembers: u32 = 1;
}

type CouncilCollective = pallet_collective::Instance1;
impl pallet_collective::Config<CouncilCollective> for Runtime {
    type Origin = Origin;
    type Proposal = Call;
    type Event = Event;
    type MotionDuration = CouncilMotionDuration;
    type MaxProposals = CouncilMaxProposals;
    type MaxMembers = CouncilMaxMembers;
    type DefaultVote = pallet_collective::PrimeDefaultVote;
    type WeightInfo = pallet_collective::weights::SubstrateWeight<Runtime>;
}

parameter_types! {
    pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
    pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
    pub const FastTrackVotingPeriod: BlockNumber = 3 * 24 * 60 * MINUTES;
    pub const MinimumDeposit: Balance = 100 * UNIT;
    pub const EnactmentPeriod: BlockNumber = 30 * 24 * 60 * MINUTES;
    pub const CooloffPeriod: BlockNumber = 28 * 24 * 60 * MINUTES;
    pub const MaxProposals: u32 = 100;
}

impl pallet_democracy::Config for Runtime {
    type CooloffPeriod = CooloffPeriod;
    type Currency = Balances;
    type EnactmentPeriod = EnactmentPeriod;
    type Event = Event;
    type FastTrackVotingPeriod = FastTrackVotingPeriod;
    type InstantAllowed = frame_support::traits::ConstBool<true>;
    type LaunchPeriod = LaunchPeriod;
    type MaxProposals = MaxProposals;
    type MaxVotes = ConstU32<100>;
    type MinimumDeposit = MinimumDeposit;
    type PreimageByteDeposit = PreimageByteDeposit;
    type Proposal = Call;
    type Scheduler = Scheduler;
    type Slash = (); 
    type WeightInfo = pallet_democracy::weights::SubstrateWeight<Runtime>;
    type VoteLockingPeriod = EnactmentPeriod; // Same as EnactmentPeriod
    type VotingPeriod = VotingPeriod;
    type ExternalDefaultOrigin = pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 1, 1>;
    type ExternalMajorityOrigin =  pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 3, 4>;
    type ExternalOrigin = pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 1, 2>;
    type FastTrackOrigin = EnsureRoot<AccountId>;
    type InstantOrigin = EnsureRoot<AccountId>;
    type PalletsOrigin = OriginCaller;
    type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 2, 3>;
    type CancelProposalOrigin = EitherOfDiverse<EnsureRoot<AccountId>,  pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 1, 1>>;
    type BlacklistOrigin = EnsureRoot<AccountId>;
    type VetoOrigin = pallet_collective::EnsureMember<AccountId, CouncilCollective>;
}

1 Answer 1

3

When a Democracy proposal executes, it dispatches the call using the Root origin:

let res = proposal
    .dispatch(frame_system::RawOrigin::Root.into())
    .map(|_| ())
    .map_err(|e| e.error);

This means you need to call extrinsics which expect the Root origin, else you will get a BadOrigin error.

For example, the transfer function in Balances expects a signed origin:

#[pallet::weight(T::WeightInfo::transfer())]
pub fn transfer(
    origin: OriginFor<T>,
    dest: AccountIdLookupOf<T>,
    #[pallet::compact] value: T::Balance,
) -> DispatchResultWithPostInfo {
    let transactor = ensure_signed(origin)?;
    // -- snip --
}

And this makes sense, because if you execute a transfer from a democracy proposal, who will be the sender account which has the original balance to be transferred?

You might want to try calling force_transfer instead, which is specifically a Root call for transferring the balance on behalf of another user:

/// Exactly as `transfer`, except the origin must be root and the source account may be
/// specified.
/// # <weight>
/// - Same as transfer, but additional read and write because the source account is not
///   assumed to be in the overlay.
/// # </weight>
#[pallet::weight(T::WeightInfo::force_transfer())]
pub fn force_transfer(
    origin: OriginFor<T>,
    source: AccountIdLookupOf<T>,
    dest: AccountIdLookupOf<T>,
    #[pallet::compact] value: T::Balance,
) -> DispatchResultWithPostInfo {
    ensure_root(origin)?;
    let source = T::Lookup::lookup(source)?;
    let dest = T::Lookup::lookup(dest)?;
    <Self as Currency<_>>::transfer(
        &source,
        &dest,
        value,
        ExistenceRequirement::AllowDeath,
    )?;
    Ok(().into())
}
1
  • Thanks, that did it, I had suspected as much but wasn't sure. Aug 24, 2022 at 16:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.