Thanks for writing this up @marcelo.near !
I have a suggestion for the Promise
data structure and corresponding interfaces of the precompile and async contract.
I think Promise
should be defined as a recursive data structure
pub enum Promise {
Then {base: Box<Promise>, callback: Box<Promise>},
And(Box<Promise>, Box<Promise>),
Call {target: AccountId, ...}
}
This makes writing invalid promises impossible. When the combinators are done via the index indirection it would be possible to write a promise that depends on an index that does not exist, or even was created as part of a different batch (which could maybe lead to some frontrunning problems?).
With this change the interfaces also become simpler because only a single identifier needs to be returned by the schedule
call, and similarly only a single identifier needs to be passed to the execute
call.
trait AuroraRouter {
pub fn schedule(&mut self, promise: Promise) -> PromiseId;
pub fn pull(&mut self, promise: PromiseId, total_gas: Gas, total_balance: Balance) -> Option<Promise>;
}
trait AsyncAurora {
pub fn pull_and_execute(&mut self, aurora: AccountId, promise: PromiseId, total_gas: Gas, total_balance: Balance);
pub fn submit_and_execute(&mut self, aurora: AccountId, submit_payload: Vec<u8>, total_gas: Gas, total_balance: Balance);
}
Note that it is still possible to put multiple calls in a single promise because And
and Then
combinators are baked in to the Promise
type. For example instead of passing vec![0, 1]
to execute
I would call schedule
with Promise::And(..., ...)
, getting back a single identifier to pass to execute
that still does both promises.