feat(standards): Add DelayedExecution (Timelocked Accounts) to AuthMultisigSmart#3044
feat(standards): Add DelayedExecution (Timelocked Accounts) to AuthMultisigSmart#3044onurinanc wants to merge 14 commits into
DelayedExecution (Timelocked Accounts) to AuthMultisigSmart#3044Conversation
…ution' into onur-multisig-smart-delayed-execution
PhilippGackstatter
left a comment
There was a problem hiding this comment.
I left a few questions/comments/nits. Overall, I'm having a hard time wrapping my head around this PR. In particular:
- it seems we have multiple names for the same concept - would be great to unify.
- I find the use of the pending slots hard to follow, and I'm not sure I understand why we need these. I left a question/suggestion to use auth args instead.
- I don't understand why we need the
cancel_and_proposefunctionality when we already have the individualcancelandpropose.
| # Map entries: [TX_HASH] -> [unlock_timestamp, proposal_timestamp, min_cancel_sigs, 1] | ||
| const TX_PROPOSALS_SLOT = word("miden::standards::auth::multisig_smart::tx_proposals") |
There was a problem hiding this comment.
Nit: I'd call this TX_SUMMARY_COMMITMENT instead of TX_HASH for accuracy (everywhere it is used in this PR).
| #! Inputs: [min_delay, propose_expiration_delta] | ||
| #! Outputs: [] | ||
| #! | ||
| #! Side effects: | ||
| #! - Writes DELAYED_EXECUTION_SLOT with word | ||
| #! `[min_delay, propose_expiration_delta, 0, 0]` (see `get_delayed_execution`). | ||
| #! |
There was a problem hiding this comment.
Nit: I think we should rather add a Where: clause explaining the inputs rather than the side effect which can be checked by looking at the code.
| #! Loads the delayed execution policy configuration from `DELAYED_EXECUTION_SLOT`. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [min_delay, propose_expiration_delta, 0, 0] |
There was a problem hiding this comment.
Nit: Returning the zeros is unnecessary.
| proc get_propose_expiration_delta | ||
| exec.get_delayed_execution | ||
| # => [min_delay, propose_expiration_delta, 0, 0] |
There was a problem hiding this comment.
| proc get_propose_expiration_delta | |
| exec.get_delayed_execution | |
| # => [min_delay, propose_expiration_delta, 0, 0] | |
| proc get_propose_expiration_delta | |
| exec.get_delayed_execution_config | |
| # => [min_delay, propose_expiration_delta, 0, 0] |
Nit: Maybe the current procedure name could be a bit clearer.
| proc apply_expiration_delta | ||
| dup neq.0 assert.err=ERR_EXPIRATION_DELTA_ZERO | ||
| # => [expiration_delta] | ||
|
|
||
| exec.tx::update_expiration_block_delta | ||
| # => [] | ||
|
|
||
| exec.tx::get_expiration_block_delta | ||
| # => [tx_expiration_delta] | ||
|
|
||
| dup neq.0 assert.err=ERR_TX_EXPIRATION_DELTA_NOT_SET | ||
| # => [tx_expiration_delta] | ||
|
|
||
| drop | ||
| # => [] | ||
| end |
There was a problem hiding this comment.
I don't understand why we set and then get the delta - this seems redundant. The neq.0 check is already done in tx::update_expiration_block_delta (but it is not documented in a Panics if section - if you don't mind, we could add it in this PR).
So, I think the whole procedure can be removed in favor of calling tx::update_expiration_block_delta directly.
| #! Finalizes a pending cancel action for the current transaction. | ||
| #! | ||
| #! Inputs: [num_verified_signatures, TX_HASH] | ||
| #! Outputs: [num_verified_signatures, TX_HASH] |
There was a problem hiding this comment.
Does this procedure use TX_HASH? If not, can we remove it from the inputs/outputs? I'd also pass in num_verified_signatures but not return it. Caller should dup it.
| #! Marks that this tx intends to execute a proposed action. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [] | ||
| #! | ||
| #! Panics if: | ||
| #! - `PENDING_EXECUTE_SLOT` is already non-empty (`ERR_PENDING_ALREADY_SET`). | ||
| #! | ||
| #! Side effects: | ||
| #! - Writes `PENDING_EXECUTE_FLAG` to `PENDING_EXECUTE_SLOT`. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc execute_proposed_transaction |
There was a problem hiding this comment.
Is the use of this procedure to communicate to the auth procedure that the current transaction executed a proposed action? It seems like this is intended to be called from a tx script, which is arbitrarily provided by the tx executor.
If so, can we not achieve the same by passing a flag as AUTH_ARGS? And if so, can we get rid of all the pending slots by using this pattern?
There was a problem hiding this comment.
I think the same pattern needs to stay for propose/cancel, so it might be good keeping execute on the same pattern for symmetry. I'm not sure if removing all the pending slots by using the AUTH_ARGS pattern works.
| #! Returns the pending execute transaction summary hash, or `EMPTY_WORD` if none is set. | ||
| #! | ||
| #! Inputs: [] | ||
| #! Outputs: [PENDING_EXECUTE_HASH] | ||
| #! | ||
| #! Where: | ||
| #! - PENDING_EXECUTE_HASH is the pending execute transaction summary hash, or | ||
| #! `EMPTY_WORD` if no execute action is pending. | ||
| #! | ||
| #! Invocation: exec | ||
| proc get_pending_execute |
There was a problem hiding this comment.
Is this procedure description accurate? In set_pending_execute we write PENDING_EXECUTE_FLAG and not a HASH, so there is a mismatch.
| #! - Writes `NEW_TX_HASH` to `PENDING_PROPOSE_SLOT`. | ||
| #! | ||
| #! Invocation: exec | ||
| pub proc cancel_and_propose_new_transaction |
There was a problem hiding this comment.
Is the functionality of this procedure not the same as calling cancel and then propose? Why do we need a single procedure for this?
| exec.delayed_execution::finalize_timelock_proposals | ||
| # => [TX_SUMMARY_COMMITMENT] |
There was a problem hiding this comment.
Given that we clear the pending slots as the last step, the tx summary we computed earlier contains the changes to the pending slots. This works in principle, but feels a bit unclean. Mentioning in case this was not intended.
| #! Panics if: | ||
| #! - block_height_delta is not a valid `u32` (`ERR_TX_INVALID_EXPIRATION_DELTA`). | ||
| #! - block_height_delta is zero (`ERR_TX_INVALID_EXPIRATION_DELTA`). | ||
| #! - block_height_delta is greater than 0xFFFF (`ERR_TX_INVALID_EXPIRATION_DELTA`). |
There was a problem hiding this comment.
Nit: We usually don't mention the exact error variants.
| pub proc cancel_transaction_proposal | ||
| dupw | ||
| # => [TX_SUMMARY_COMMITMENT, TX_SUMMARY_COMMITMENT] | ||
|
|
||
| exec.is_tx_proposed | ||
| # => [is_proposed, TX_SUMMARY_COMMITMENT] | ||
|
|
||
| assert.err=ERR_TX_NOT_PROPOSED | ||
| # => [TX_SUMMARY_COMMITMENT] | ||
|
|
||
| exec.set_pending_cancel | ||
| # => [] | ||
| end |
There was a problem hiding this comment.
Afaict, propose_transaction and cancel_transaction_proposal are both essentially setters for their provided arguments; writing into a temporary storage slot. The arguments come from a tx script, which is constructed by a user. The main work is done in the auth procedure, which reads the slots, enforces delay constraints and updates the maps.
If we do not need these procedures to be called from a note, which I assume is not the case, we could reduce complexity by a lot by removing these procedures and setting AUTH_ARGS to the hash of the following data:
[delay_action, CANCEL_TX_SUMMARY_COMMITIMENT, PROPOSE_TX_SUMMARY_COMMITIMENT, SALT]
where delay_action is one of {execute, propose, cancel}. We then dispatch based on delay_action.
That way, we could handle all logic in one flow within the auth procedure rather than splitting it across a pre-auth and a post-auth phase, and avoid having three temporary storage slots.
Whether the user of the multisig constructs a tx script and calls these APIs or whether they construct AUTH_ARGS in the above way should be essentially equivalent.
Any thoughts? Would this work?
There was a problem hiding this comment.
If we do not need these procedures to be called from a note, which I assume is not the case, we could reduce complexity by a lot by removing these procedures and setting AUTH_ARGS to the hash of the following data:
I was thinking that we might somehow extend delayed execution logic into note-based auth, however, as I explore more about this I've seen that it's not possible to use the same logic as this design requires num_signatures, and this is not the case for owner controlled and rbac.
So, although I believe the current design is easier to read, your proposed way of constructing AUTH_ARGS reduces the complexity, and API calls are easier to use. So, it seems it is better to switch to your proposed design.
There was a problem hiding this comment.
Since this is a bigger refactor, I'd wait for @bobbinth or @mmagician's input before executing on this.
Closes: #3043.
Previously opened a draft PR implementing all features regarding Smart Multisig here: #2973. That PR still relatively big, and we separate the delayed execution logic into this PR.