Stepping from a typed view on outputs ("one is either a voucher or a notice") to a more generalized view ("an arbitrary blob, which may encode a function call, and our contracts currently support these two function signatures"), we noticed we can create an intermediary step.
Regardless of the view, we can divide outputs into two disjoint categories: executable and non-executable. What it means to execute an output? Well, we don't know, yet. We know what it is to execute a voucher: it does a message call to another contract. We can also foresee another type of executable output, the DELEGATECALL voucher, which does a delegate call to another contract.
✔️ Solution
Why is this view interesting for us? Because we can abstract away output types at the interface level. So, instead of having executeVoucher, executeDelegateCallVoucher, etc., we can define a single entry point for executing any output, namely executeOutput. But how is such a thing possible, if outputs may have different fields, etc.? Let it accept an arbitrary blob and decode it on-the-run.
So, instead of having...
function executeOutput1([fields1...], Proof calldata proof) external;
function executeOutput2([fields2...], Proof calldata proof) external;
// ...
function executeOutput4([fields4...], Proof calldata proof) external;
We can have...
function executeOutput(bytes calldata output, Proof calldata proof) external;
It's worth mentioning that if you try to call this function with an invalid output blob or with a blob of a non-executable output, it should revert with a nice error message.
Likewise, we can generalize the validateNotice function to any output. The implementation of such a function should be completely agnostic to the contents of the output blob, as it will simply hash it and check if the proof is sound.
function validateOutput(bytes calldata output, Proof calldata proof) external view;
With this, we can also generalize the wasVoucherExecuted function...
function wasOutputExecuted(uint256 inputIndex, uint256 outputIndexWithinInput) external view returns (bool);
This means that the bitmap we currently use for vouchers will be used for any executable output.
📚 Context
Stepping from a typed view on outputs ("one is either a voucher or a notice") to a more generalized view ("an arbitrary blob, which may encode a function call, and our contracts currently support these two function signatures"), we noticed we can create an intermediary step.
Regardless of the view, we can divide outputs into two disjoint categories: executable and non-executable. What it means to execute an output? Well, we don't know, yet. We know what it is to execute a voucher: it does a message call to another contract. We can also foresee another type of executable output, the
DELEGATECALL
voucher, which does a delegate call to another contract.✔️ Solution
Why is this view interesting for us? Because we can abstract away output types at the interface level. So, instead of having
executeVoucher
,executeDelegateCallVoucher
, etc., we can define a single entry point for executing any output, namelyexecuteOutput
. But how is such a thing possible, if outputs may have different fields, etc.? Let it accept an arbitrary blob and decode it on-the-run.So, instead of having...
We can have...
It's worth mentioning that if you try to call this function with an invalid output blob or with a blob of a non-executable output, it should revert with a nice error message.
Likewise, we can generalize the
validateNotice
function to any output. The implementation of such a function should be completely agnostic to the contents of the output blob, as it will simply hash it and check if the proof is sound.With this, we can also generalize the
wasVoucherExecuted
function...This means that the bitmap we currently use for vouchers will be used for any executable output.