Cross Contract Calls

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.

2 Likes