The Mandate pallet provides a simple governance mechanism that allows approved origins to execute privileged (root-level) operations on the Avail blockchain.
Overview
Mandate enables:
- Execution of root-level calls from approved governance origins
- Bypassing of normal transaction filters for privileged operations
- Fee-free execution for governance-approved actions
- Transparent logging of all root operations
The Mandate pallet is similar to Substrate’s Sudo pallet but uses a configurable ApprovedOrigin instead of a single sudo key, enabling more flexible governance models.
Architecture
The pallet acts as a wrapper that:
- Verifies the origin is approved (e.g., collective, council, democracy)
- Dispatches the call with root privileges
- Bypasses transaction filters
- Emits an event with the execution result
- Returns no fees to the caller
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter + UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin> + GetDispatchInfo;
type WeightInfo: WeightInfo;
type ApprovedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
Extrinsics
mandate
Executes a call with root privileges if the origin is approved.
Must satisfy the ApprovedOrigin requirement (e.g., Council, TechnicalCommittee, Democracy)
The call to execute with root privileges
use frame_support::traits::Get;
// Example: Execute a runtime upgrade
let upgrade_call = RuntimeCall::System(SystemCall::set_code { code });
let mandate_call = Call::mandate {
call: Box::new(upgrade_call)
};
mandate_call.dispatch(council_origin)?;
// Example: Update configuration
let config_call = RuntimeCall::DataAvailability(
DataAvailabilityCall::set_submit_data_fee_modifier { modifier }
);
let mandate_call = Call::mandate {
call: Box::new(config_call)
};
mandate_call.dispatch(approved_origin)?;
Execution Flow:
pub fn mandate(
origin: OriginFor<T>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
// 1. Verify origin is approved
T::ApprovedOrigin::ensure_origin(origin)?;
// 2. Execute with root privileges, bypassing filters
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
// 3. Emit event with result
Self::deposit_event(Event::RootOp {
result: res.map(|_| ()).map_err(|e| e.error),
});
// 4. No fee charged
Ok(Pays::No.into())
}
Weight Calculation:
The weight of mandate is the base mandate weight plus the weight of the wrapped call:
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::mandate().saturating_add(dispatch_info.weight),
dispatch_info.class
)
})]
Return Value:
Ok(Pays::No.into()) - Execution completed (check event for success/failure), no fee charged
Events:
RootOp { result } - Emitted after execution with Ok(()) or Err(error)
Errors:
BadOrigin - Origin does not satisfy ApprovedOrigin requirement
- Propagates any errors from the wrapped call (logged in event, not returned)
Events
Event Structure:
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A root operation was executed, show result
RootOp { result: DispatchResult },
}
Monitoring the RootOp event allows external systems to track:
- Which root operations were attempted
- Whether they succeeded or failed
- What errors occurred (if any)
Configuration
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// A sudo-able call (any dispatchable)
type RuntimeCall: Parameter
+ UnfilteredDispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo;
/// Weight information for extrinsics
type WeightInfo: WeightInfo;
/// Origin that can execute mandate (e.g., Council, TechnicalCommittee, EnsureRoot)
type ApprovedOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
ApprovedOrigin Examples
Single Root Account:
type ApprovedOrigin = EnsureRoot<AccountId>;
Council Majority:
type ApprovedOrigin = EnsureOneOf<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 1, 2>
>;
Technical Committee:
type ApprovedOrigin = EnsureOneOf<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionAtLeast<AccountId, TechnicalCollective, 2, 3>
>;
Democracy:
type ApprovedOrigin = EnsureOneOf<
EnsureRoot<AccountId>,
pallet_democracy::EnsureReferendumPassed
>;
Usage Examples
Runtime Upgrade
// Prepare the new runtime WASM code
let new_code: Vec<u8> = include_bytes!("../runtime.wasm").to_vec();
// Create the set_code call
let upgrade = RuntimeCall::System(SystemCall::set_code {
code: new_code,
});
// Execute via mandate
Mandate::mandate(council_origin, Box::new(upgrade))?;
// Monitor event:
// RootOp { result: Ok(()) }
Update Protocol Parameters
// Update block dimensions
let update_dimensions = RuntimeCall::DataAvailability(
DataAvailabilityCall::submit_block_length_proposal {
rows: 512,
cols: 512,
}
);
Mandate::mandate(approved_origin, Box::new(update_dimensions))?;
// Update fee modifier
let update_fees = RuntimeCall::DataAvailability(
DataAvailabilityCall::set_submit_data_fee_modifier {
modifier: DispatchFeeModifier::new(/* ... */),
}
);
Mandate::mandate(approved_origin, Box::new(update_fees))?;
Vector Bridge Configuration
// Update whitelisted domains
let domains = BoundedVec::try_from(vec![1u32, 10u32, 137u32])?; // Ethereum, Optimism, Polygon
let update_domains = RuntimeCall::Vector(
VectorCall::set_whitelisted_domains { value: domains }
);
Mandate::mandate(approved_origin, Box::new(update_domains))?;
// Set broadcaster address
let set_broadcaster = RuntimeCall::Vector(
VectorCall::set_broadcaster {
broadcaster_domain: 1,
broadcaster: H256::from_slice(&broadcaster_address),
}
);
Mandate::mandate(approved_origin, Box::new(set_broadcaster))?;
Emergency Actions
// Freeze a source chain (emergency)
let freeze_chain = RuntimeCall::Vector(
VectorCall::source_chain_froze {
source_chain_id: 1,
frozen: true,
}
);
Mandate::mandate(emergency_origin, Box::new(freeze_chain))?;
Batch Operations
use pallet_utility::Call as UtilityCall;
// Execute multiple calls atomically
let calls = vec![
RuntimeCall::DataAvailability(/* ... */),
RuntimeCall::Vector(/* ... */),
RuntimeCall::System(/* ... */),
];
let batch = RuntimeCall::Utility(
UtilityCall::batch_all { calls }
);
Mandate::mandate(approved_origin, Box::new(batch))?;
Governance Integration
Council Proposal
// TypeScript example using Polkadot.js
const proposal = api.tx.dataAvailability.submitBlockLengthProposal(512, 512);
const mandateCall = api.tx.mandate.mandate(proposal);
// Council member proposes
const proposalHash = mandateCall.method.hash;
const threshold = 3; // 3 out of 5 council members
await api.tx.council
.propose(threshold, mandateCall, mandateCall.encodedLength)
.signAndSend(councilMember);
// Other council members vote
await api.tx.council.vote(proposalHash, proposalIndex, true).signAndSend(member2);
await api.tx.council.vote(proposalHash, proposalIndex, true).signAndSend(member3);
// Execute when threshold reached
await api.tx.council
.close(proposalHash, proposalIndex, refTime, lengthBound)
.signAndSend(anyAccount);
Democracy Referendum
// Submit via democracy
const proposal = api.tx.dataAvailability.setSubmitDataFeeModifier(modifier);
const mandateCall = api.tx.mandate.mandate(proposal);
// Propose referendum
await api.tx.democracy
.propose(mandateCall, depositAmount)
.signAndSend(proposer);
// After referendum passes, execute
await api.tx.democracy
.enactProposal(proposalHash, referendumIndex)
.signAndSend(anyAccount);
Security Considerations
The Mandate pallet grants full root access to the blockchain. Proper configuration of ApprovedOrigin is critical.
Best Practices:
-
Multi-signature Governance: Use collective pallets (Council, TechnicalCommittee) with threshold requirements
-
Time Delays: Consider integrating with
pallet_scheduler for delayed execution:
let delayed_call = RuntimeCall::Scheduler(
SchedulerCall::schedule {
when: current_block + DELAY_BLOCKS,
call: Box::new(mandate_call),
}
);
-
Event Monitoring: Always monitor
RootOp events in production:
api.query.system.events((events) => {
events.forEach((record) => {
const { event } = record;
if (event.section === 'mandate' && event.method === 'RootOp') {
const result = event.data[0];
console.log('Root operation executed:', result.toString());
// Alert/log for security monitoring
}
});
});
-
Gradual Rollout: For new governance models, start with higher thresholds and lower them gradually
-
Fallback Mechanism: Maintain an emergency root key secured offline as ultimate fallback
Comparison with Sudo
| Feature | Mandate | Sudo |
|---|
| Single Key | No | Yes |
| Configurable Origin | Yes | No |
| Multi-sig Support | Yes | No |
| Collective Governance | Yes | No |
| Fee Behavior | No fees | No fees |
| Filter Bypass | Yes | Yes |
| Event Logging | RootOp | Sudid |
| Production Ready | Yes | Development only |
Runtime Integration
// runtime/src/lib.rs
impl pallet_mandate::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type WeightInfo = pallet_mandate::weights::SubstrateWeight<Runtime>;
// Example: Require 2/3 council approval
type ApprovedOrigin = EnsureOneOf<
EnsureRoot<AccountId>,
pallet_collective::EnsureProportionAtLeast<AccountId, CouncilCollective, 2, 3>,
>;
}
construct_runtime!(
pub enum Runtime {
System: frame_system,
Mandate: pallet_mandate,
Council: pallet_collective::<Instance1>,
// ... other pallets
}
);