Hi there!
I wanted to try out the upgradability feature with cis2-wccd example.
When I changed the logic slightly without changing the state parameter, there was no problem.
However, when I upgraded to add new parameters to the state, the upgrade itself was successful, but after that, calling any update function(even view function) results in an error like Error: Updating contract instance failed: Runtime failure.
The key point is that the high-level intefrace (the default one) tries to do things for you, such as make sure that you always have a consistent view of the state. However that means that it would try to deserialize state in the old shape after the migration function returns. And of course that does not work.
The low-level interface is more direct, and does not do things for you implicitly. And this is what you want in this instance.
Sorry for not responding sooner and thanks for the example.
I think I almost understand and now interested in the low-level interface.
Are there any other use cases for low-level interface?
The low-level interface allows you more control, but also needs you to be aware of more.
For example, in the normal, high-level interface, when you .invoke_contract then
any local changes you have made to the state are committed to the persistent state
a contract is invoked
if the state of the invoking contract has been changed then the host.state() is reloaded from persistent storage.
The low-level interface just invokes another contract, and it is up to the contract writer to be careful and do steps (1) and/or (3) if necessary.
However that does mean you can write more efficient contracts since you may know that some operations are not necessary. That is the primary motivation for the low-level interface.
I would recommend to only use the low-level interface sparingly and not try to optimize too much too early. There are safety features built into the high-level interface to prevent many common mistakes.