Feat: add automatic transfers feature
This can be done by creating a new pallet named manual_scheduler
.
The main idea it to declare onchain an authorization to execute a specific call when a specific blockchain tine or block number will be reached.
Then, any signed origin can execute any authorized call (on behalf of the initial origin) when the time point is reach. This manual execution should update the next_time
and decrease occurrences
.
The execute()
can be something like that:
//
#[pallet::weight((
T::WeightInfo::execute(
*length_bound,
).saturating_add(call.get_dispatch_info().weight)
// For inner call origin AccountData (whitelisted in direct calls)
.saturating_add(T::DbWeight::get().reads_writes(1, 1)),
))]
fn execute(
origin: OriginFor<T>,
task_id: T::taskId,
// We need the call as parameter to be able to account the weight of the call
call: Box<<T as Config>::Call>,
// We need the lenght of the call as a parameter to be able to take it into account in weights computation
#[pallet::compact] length_bound: u32
) -> DispatchResultWithPostInfo {
// Verification phase
let _ = ensure_signed(origin)?;
let mut task = Tasks::get(task_id).ok_or(Error::TaskNotExist)?;
// Anyone can execute just before you, and made your legit tx fail,
// we don't want to charge the call weights in this case
ensure!(task.next_time is reach, DispatchErrorWithPostInfo {
post_info: Some(execute_base_weight).into(),
error: Error::TooEarly
});
let call_hash = call.using_encoded(blake2_256);
let call_len = call.using_encoded(|bytes| bytes.len());
ensure!(call_hash == tack.call_hash, Error::InvalidCall);
let origin = Origin::signed(task.owner);
// Execution phase
let result = call.dispatch(origin);
deposit_event(TaskExecuted(task_id, result));
if task.remaining_occurrences > 1 {
task.remaining_occurrences -= 1;
task.next_time += task.period;
Tasks::insert(task_id, task);
} else {
Tasks::remove(task_id);
deposit_event(TaskEnd(task_id));
}
// We should return the actual weight really consumed by the dispatched call
Ok(get_result_weight(result)
.map(|call_actual_weight| {
T::WeightInfo::execute(
call_len as u32,
)
.saturating_add(call_actual_weight)
})
.into())
}
This design is better because it's extremely safer and easier to manage manual execution rather than automatic execution.
The end user can (should) pay one (or several) external provider to trigger the execution.