Request for comments on the views proposal. The comments can be left below.
Why do we need GET_STORAGE
if we have VIEW
?
The existence of GET_STORAGE
means that the storage type of a contract becomes definitely part of its observable behavior.
So, for example, a compiler cannot modify the storage type (perhaps optimizing things away, or adding things) while preserving semantics. Indeed, two contracts will be semantically equivalent only if they have identical storage types (presumably including annotations, unless they are eliminated someday, or the GET_STORAGE
check ignores them as a special case.)
Presumably, GET_STORAGE
and VIEW
will be forbidden to involve ticket
or any other future non-DUPable/“forged” types (for VIEW
, in the return type)?
If so, GET_STORAGE
will be completely unusable on any contract whose storage involves ticket
?
VIEW
requires the targeted contract to prepare “function” and GET_STORAGE
allows us to receive storage directly from the targeted contract.
The type for GET_STORAGE
is necessary and observable since Michelson is typechecked and it is a part of definitions of contract.
Currently, GET_STORAGE
and VIEW
aren’t forbidden that since these two instructions are read-only. They can’t modify the storage.
What’s the concern for forbidden that?
Yes. The new thing to me is that the storage
type of a contract becomes observable to other contracts, like the parameter type is now, or like the types of views would (and should) be. In other words, the storage type becomes part of the on-chain “public interface” of a contract. Personally, I like that the storage type is “private.”
The most obvious implementations of GET_STORAGE
and VIEW
with ticket
in the return type would violate the intended resource semantics of tickets. It would allow a contract to receive copies of tickets while leaving the original tickets in the other contract’s storage. For GET_STORAGE
the ownership semantics would be completely destroyed, as any contract could steal copies of any other contract’s tickets.
Maybe, instead of forbidding tickets, one could walk the storage value, replacing any tickets with some kind of dummy tickets. (Maybe replace the ticketer address with a dummy address…?) This would have to be done lazily for tickets contained in big_maps too, I guess.
Please correct me if I am wrong. I think it isn’t a issue because when inspected the value of the received ticket by GET_STORAGE
, VIEW
or others, it will include the amount, and the address of the ticketer, i.e. the contract that created the ticket. So, we can’t really “forge” and pretend a ticket which has been created by another ticketer.
Tickets are intended to have a resource interpretation, like linear logic, or affine really; they can be DROP
’d, but:
VIEW
should not provide a way for a contract (or others) to effectively DUP
its tickets, and GET_STORAGE
definitely should not provide a way for other contracts to DUP
a contract’s tickets without its consent.
Anyway, my personal recommendation is to drop GET_STORAGE
and to forbid ticket
(allow_forged:false
) in the return type of views.
The only specific (not very convincing) motivation I have heard for GET_STORAGE
is that it would provide access to the storage data of “adversarial” contracts. But if ticket
is forbidden for GET_STORAGE
, and such adversarial contracts could simply include a ticket
in their storage type to block the use of GET_STORAGE
, then it wouldn’t serve that purpose.
There could be other solutions, though…
For conservation of tickets, it also seems necessary to forbid operation
in the return type of view
, since tickets can be smuggled under transfers or originations. (In any case, operation
in a view seems questionable…)
Other miscellaneous questions arise too: are all the various transaction-context instructions like SENDER
, AMOUNT
, BALANCE
, SELF_ADDRESS
(but not SELF
), etc… allowed inside view
? Do they have the same values seen by the caller? Maybe some of them (BALANCE
? SELF_ADDRESS
and SELF
?) take values pertaining to the callee?
It is not only about adversarial contracts.
It can be about retroactively adding new views to the contract: the information is already there, but the view was not written before, and we want to use that new information.
But yeah. We will drop GET_STORAGE
as a result. I do not believe we can find a good model for it (stubbing tickets? what about big maps??).
There is actually a good one. Replacing ticket
types with ticket_stub
in GET_STORAGE
's return. The data itself doesn’t need to change.
For views, yup, tickets and wrappers (big maps or operations) have to be forbidden.
Yuck. But it does sound workable to me…
At the moment, because tickets are represented as Pair x y z
, you could simply replace ticket t
with the opened_ticket_type
(pair address t nat
.)
But, we might like to introduce a Ticket
tag in the future, to reduce risk from type confusion bugs (e.g. like the original VIEW draft.) In that case, ticket_stub
must be a new type, and will have to parse Ticket
(so that “the data itself doesn’t need to change”.)
Actually, I’m not sure this strategy will avoid all risk from type confusion…
Unrelatedly:
A possible hazard with VIEW
is calling it on yourself. It should be well-known that doing this intentionally is an evil (and currently inefficient) hack, and that the view will see a “stale” storage value.
But a more difficult hazard is that you must consider unknowingly calling VIEW
on yourself. Generally, this might happen whenever you use a this might happen whenever any view you call (directly or indirectly) ends up calling one of your views.view
interface which you also implement.
I suppose you can call this a “reentrancy” hazard, which remains even though VIEW
is “read-only”.
Should calling VIEW
on yourself be forbidden? I don’t know… This will turn the safety hazards – and innocent examples – into liveness hazards.