Optimising map_new_from_slices for optional / void values
#1750
Replies: 5 comments 4 replies
-
|
I like the suggestion approach in general. The only thing I'm not sure about is whether we should be changing the semantics of the existing host function. This seems a bit risky to me, even though I agree that it seems hard to come up with the exact scenario where this could introduce a vulnerability or a logic bug. Still, the fact that we can't easily come up with a scenario doesn't mean that there will be no such scenario.
This is not an argument for modifying the existing function, is it? Nothing prevents us from introducing a new host function in p24 and then using it in SDK v24 macro implementations. This way we guarantee that the old contracts behave as expected, and the new contracts can make an educated decision about using the new semantics when performing an upgrade. |
Beta Was this translation helpful? Give feedback.
-
|
Would it be possible to solve this at the sdk level? I.e. in the macro generation code check for Void type and skipping entries. If so it would be easier and more future proof than adding a new host function. |
Beta Was this translation helpful? Give feedback.
-
|
I have updated the description at the top to note that two host functions need modifying or two new host functions need creating. The proposal originally only mentioned |
Beta Was this translation helpful? Give feedback.
-
|
@tomerweller asked what the cost-benefit of this change would be. For token events, the main use case is the For all contracts, any optional fields will reduce storage entries by a similar amount. @tomerweller I think the benefit is there, more so for migration of storage data than for the original use case where the idea was birthed (event publishing). Opening up the ability to do incremental migration easier using optional fields is a meaningful improvement. Today to do incremental migration when adding new fields, two types have to be defined and there must be a way to know that you need to read one vs the other, which means storing parallel to every type a version marker of sorts. This is a robust strategy for doing migration. But less natural and obvious than simply adding an optional field on a type. |
Beta Was this translation helpful? Give feedback.
-
|
I am closing this proposal, in favour of a new proposal that I'd like to be considered instead: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I propose that in a future version of the protocol the
map_new_from_slicesandmap_unpack_to_slicefunction be modified so that when writing or reading the map of values:Some context:
Option<T>is a Rust type that in Soroban is converted into the following values:Somevalue containing a value of typeT, the option is converted into a Soroban value that matches theTtype.Nonevalue that does not contain a value ofT, the option is converted into a Soroban 'void' value.Whenever any
contracttypestructis converted into anScVal, it is converted into a Map, and a host function specifically designed for optimised passing of maps back and forth between the guest and the host is used. That host function ismap_new_from_slices.I am in the process of implementing the
contracteventmacro, that will provide a way to define an event using a struct. In conversation for CAP-67 an idea was suggested (by @dmkozh I think?) that any SDK functionality that's built to create events where you can specify optional data fields in a map, should omit key-value pairs where the optional data field has no value. It would be convenient, intuitive, and a reasonable optimisation.Why:
What I've found whilst experimenting with a couple different implementations of a
contracteventmacro is that we will end up using the samemap_new_from_slicesthatcontracttypeuses for converting structs to maps. So if we want that behaviour of omitting fields that have a none optional, we either need to change the existing function, or add a new function that behaves similarily except for the omission part.I think the change should be made to
map_new_from_slicesrather than introduce a new function, because consistency between how data is omitted forcontracttypeandcontracteventis important. If we were to makecontracteventomit fields whilecontracttypedid not, there would be visible differences with how contract events get written if they contain a single-value that just happens to be a map, vs the event itself be a multi-value map. Whilst the network doesn't distinguish between those two event structures, contract code will.I think the optimisation of omitting key-value pairs for optionals without a value is reasonable to make to storage too, not just events. So making this change in such a way that the omission occurrs also for contracttypes is a net benefit.
Additional benefits:
An additional benefit of this change is that it will become easier to migrate data in contracts. Today once a contracttype struct is stored, it cannot gain any new fields, even if they are optional, because reading out any old value will error on the missing new field on old storage. This change would allow new fields to be missing on old stored values, as long as the type of the new field is
Option<T>.Backwards compatibility:
And as long as the read handles the case where the key-value pair is missing, it is largely backwards compatible with existing usage.
This is however a change in behaviour that would be observable in some edge cases. For example, if you rely on contract storage erroring when reading out a type that is missing a value that maps to an option, that would no longer be the case. But that case seems quite fringe.
cc @stellar/contract-committers
Beta Was this translation helpful? Give feedback.
All reactions