Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: shared mutable configurable delays (#6104)
Closes #5493, follow up of #6085. This makes the delay in SharedMutable not be fixed and instead configurable by users throughout the lifetime of the contract. This is however more complicated than it sounds at first: because private proofs are being created relying on the public values being stable until a future point in time, it must not be possible to cause for a shared value to change before some delay. Two scenarios are particularly tricky: - if the delay is reduced, then it is possible to schedule a value change with a shorter delay, violating the original delay's constraints. The solution to this is to make delay changes be scheduled actions themselves, so that the total delay (wait time for the new delay to come into effect plus the new reduced delay) equals the original delay. Note that increasing a delay cna be done instantly. - if we schedule delay changes as per the above, then we must consider a scenario in which a delay reduction is scheduled in the near future. It may happen that waiting for the reduction to come into effect and then scheduling results in a shorter delay than if the scheduling were to happen immediately - this lower 'effective delay' is the value that must be used in private proofs. ## How I had originally considered creating a sort of wrapper state variable that held two SharedMutables, one for the value and one for the delay, or alternatively two ScheduledValueChanges, but ultimately I realized that a scheduled value change is significantly different from a scheduled delay change. Namely: - the notion of the 'current' delay is meaningless in private - we only care about the 'effective' delay - there's no use for the block horizon of a delay change - scheduling a delay change requires setting a delay depending on the current and new values, not an externally defined one Due to these differences, I introduced ScheduledDelayChange, which is essentially a variant of the value change, but with these considerations baked in. I think this is a reasonable way to do things, even if at first this may seem to introduce too many concepts. It also helps with the fact that there's so many values involved (pre, post and block of change for value and delays, as well as current, effective, historical values, etc.), and with language becoming weird - we need to describe the delay for scheduling a delay change, which will later affect the delays of scheduling value changes. With ScheduledDelayChange, extending the functionality of SharedMutable was relatively straightforward. The unit tests became a bit more complicated due to there bieng more scenarios, so I also used this as an opportunity to try to create slightly more complex Noir tests. I didn't go too crazy here, but they seem to be right at the point where we'd want to introduce something like a `Test` struct with custom impls for setup, common assertions, etc. ## Problems An uninitialized `SharedMutable` has both delay and value of 0. A zero delay transforms `SharedMutable` into `PublicMutable`: scheduled value changes become effective immediately, and it is not possible to read from private since `tx.max_block_number` would equal a historical block (i.e. an already mined one). Delay initialization is therefore required, and this is typically fine: since the initial delay is 0 any change will be an increase, and therefore instant. The problem arises when we cannot have explicit initialization and instead wish to rely on defaults. This happens e.g. when we put a SharedMutable inside a `Map`: we can't initialize all entries for all keys, and we run into trouble. This is a pattern followed by `KeyRegistry` and `TokenBlacklist`: we have per-user configuration, and cant really ask users to initialize their state before interacting with the system. ## Solution? A possible solution would be to have a default value for the delay, and to store e.g. `Option<u32>` instead of plain integers and using `unwrap_or(DEFAULT)`. We could then make this a type parameter for SharedMutable, e.g. `registry: Map<Address, SharedMutable<Key, DEFAULT_DELAY>>`. This would make certain things more complicated, particularly the effective delay and delay change block of change computations, but it should all be containable within `ScheduledDelayChange`, which sounds just about right. ---- I'm keeping this is a draft so we can discuss the current approach and wether we think the above or an alternative solution would be reasonable to attempt. Note that this PR won't pass CI as some of the contracts won't build. --------- Co-authored-by: Jan Beneš <janbenes1234@gmail.com> Co-authored-by: Lasse Herskind <16536249+LHerskind@users.noreply.github.com>
- Loading branch information