Adding Read-Only Calls

Current Situation

On Tezos, we can’t “call” another Smart-Contract (SC). What I mean by that is that there is no operation to get some output from another SC. What has to be done is to convert the two contracts into Continuation Passing Style.
This was a conscious decision, made to avoid reentrancy attacks.

However, as it is, this decision has some drawbacks:

  • Reading the state from another SC requires some cooperation from it
  • Writing the code is a bother (which is being mitigated with higher-level languages, but can only be mitigated)

Proposed Improvement

Content

I suggest the addition of two Michelson instructions:

  • GET_STORAGE sc_type, which gets the storage of some SC. (Important for adversarial reads.)
  • VIEW view_name view_type, which calls a view of some SC.

A view is a function from the view parameter and the storage of the SC to some output. Views don’t change the storage nor generate operations.

Advantages

  • With GET_STORAGE, migrating to a new SC becomes much easier.
  • It is easier to define accessors in interfaces.
  • It is easier to interact between contracts in general.

Technical Details

I’m not sure about a lot of implementation details, that I will consequently leave to others. What I am doing:

  • Adding the instructions and their syntax
  • Adding view entry-points
  • Adding some tests

What needs to be done:

  • Deciding what can be outputted by views (can they generate or return big_maps?)
  • Gas
  • More tests and reviews
  • Adding relevant error messages
  • Listing contracts that will be read before applying
  • Adding RPC clients

I’ll post a message once I will have pushed to a branch.

13 Likes

This is great. As I have been looking into using NFT based solutions for identity and reputation, I ran into how limiting it can be to implement these without the ability to get the storage of another contract. I expect those working on NFT based games will also see tremendous value in adding these read only calls.

I look forward to a good Agora discussion around use cases and definitions for views and get_storage.

3 Likes

Current branch is here, there is a test for GET_STORAGE.

EDIT: Just added the first test for the view prototype.
Here are the contracts that have been tested, that show what views currently look like. The first smart-contract exposes that adds its storage to what is passed as parameter.

Smart-contract defining the view

parameter nat ;
storage nat ;
code { CAR ; NIL operation ; PAIR } ;
view "add" nat nat { UNPAIR ; ADD } ;

Smart-contract using the view

parameter (pair nat address) ;
storage nat ;
code { CAR ; UNPAIR ; VIEW "add" nat nat ; NIL operation ; PAIR } ;
2 Likes

Can you elaborate on that? Why do we need the storage copy to happen on chain?

I am afraid that the GET_STORAGE instruction could break the business model of on-chain oracles that ask for a positive amount to let other smart contracts access part of their storage.

parameter (pair nat address) ;

What happens if the contract at this address does not feature a view called “add”?

Do we need view names to be first-class? Otherwise I would suggest to use annotations instead of strings for consistency with the proposed syntax for entrypoints in Babylon.

1 Like

I am afraid that the GET_STORAGE instruction could break the business model of on-chain oracles that ask for a positive amount to let other smart contracts access part of their storage.

If it’s on-chain, it’s already available. A smart-contract that needs data from an on-chain oracle could simply exposes entry-point that asks a trusted party to do so.
GET_STORAGE makes it so that no trust is needed.

The mentioned business model will break. But breaking business models that rely on value capture through software obstacles by removing those obstacles is a feature.

Interacting with uncooperative contracts is the main point of GET_STORAGE as opposed to views.

What happens if the contract at this address does not feature a view called “add”?

I believe it fails. The full idea (this is only a prototype) would be to list the contracts that will be called before the operation, so that they can be checked for this and have their views pre-loaded.

Do we need view names to be first-class? Otherwise I would suggest to use annotations instead of strings for consistency with the proposed syntax for entrypoints in Babylon.

Not at all, you are right and I’ll follow this suggestion. (Strings were simply easier.)

Can you elaborate on that? Why do we need the storage copy to happen on chain?

I believe it could enable copying data too big to be cheaply parsed and typed. Given this is not the main advantage, I haven’t given it much more consideration and might be wrong.

I’m of two minds on this and would like to present the case against these read-only calls.

  1. The current model of message passing between contracts looks a lot like what contract calls would look like in the presence of sharding. It is not equivalent. Currently they are atomicity guarantees that would go away with sharding, but it is otherwise close. Currently, getting data from two other contracts require callbacks and keeping track internally of the state… this may seem unwieldy, but this exactly what one should expect in a sharded environment. The current call model is not exactly what you’d see in a sharded chain but it’s close, and read only calls are a step away from that.

  2. The gas cost of doing call-backs mostly comes from deserializing the entire other-contract as opposed to just one entry point, and from needlessly deserializing the contract making the first call a second time. But this can and should be fixed directly. If callbacks are gas cheap, the need for these read-only calls is lesser.

  3. It’s not clear how many applications genuinely need to get data from another contract as part of their execution. There are many instances of applications with a forward flow: something happens and triggers a series of messages which propagate through contracts ending in some outcome, but not that many of contracts needing to hear back from another one. The obvious exception are oracles, but besides that? On top of that, it makes sense for an oracle service to fulfill request asynchronously.

1 Like

I’m of two minds on this and would like to present the case against these read-only calls.

Thanks!

The current model of message passing between contracts looks a lot like what contract calls would look like in the presence of sharding. It is not equivalent. Currently they are atomicity guarantees that would go away with sharding, but it is otherwise close. Currently, getting data from two other contracts require callbacks and keeping track internally of the state… this may seem unwieldy, but this exactly what one should expect in a sharded environment. The current call model is not exactly what you’d see in a sharded chain but it’s close, and read only calls are a step away from that.

I don’t know how much sharding proposals rely on smart-contracts internally managing that partial state. Do you have any link/reference?
If they massively rely on this, then indeed, a view suggestion that doesn’t take care of this is incomplete.

The previous objections I heard about sharding were only about knowing at run-time which contracts a given one would call. But that can be dealt with by having a list of pre-committed contracts by operation.

The gas cost of doing call-backs mostly comes from deserializing the entire other-contract as opposed to just one entry point, and from needlessly deserializing the contract making the first call a second time. But this can and should be fixed directly. If callbacks are gas cheap, the need for these read-only calls is lesser.

Agreed, this needs to be fixed. And so, regardless of views being implemented or not.

It’s not clear how many applications genuinely need to get data from another contract as part of their execution. There are many instances of applications with a forward flow: something happens and triggers a series of messages which propagate through contracts ending in some outcome, but not that many of contracts needing to hear back from another one. The obvious exception are oracles, but besides that? On top of that, it makes sense for an oracle service to fulfill request asynchronously.

It might be true, but I believe explicitly forbidding experimentation and contracts deemed non-genuine, even though a lot of people explicitly requested this, should be motivated by much stronger considerations.

Also, views are about getting info from other contracts. I tend to think about views as a generalization of oracles (parametrized oracles!), so I’m not sure how much of a “besides that” we should be looking for.

Some general remarks:

What is “the view parameter”? Not yet defined.
If defined later, maye just write “a /view parameter/”.

What is “adversial reads”? Can you give a link?

I see how a view can be seen as a function in the general sense, but since the rest of the paragraph is the context of Michelson, that has no functions, this may be slightly confusing. Later you write that you’re adding “view entry points”. If a view is an entry point, then I think it is clearer to write that

  • a view is an entry point.
  • the parameter of that entry point is called the view parameter

There is an existing “tezos improvement proposal” that proposes a convention for how to implement something called views using CPS. Your proposal might benefit from a discussions of similarities / differences.

I assume, that if the view fails, then the calling contract also fails.

By that he means a contract directly putting the storage of another contract on its own stack. It’s “adversarial” because the other contract could have kindly set up views to allow access to its state but didn’t do so, therefore you have to directly grab its raw storage state.

I’m not convinced that allowing these is a good idea, I’m not convinced it’s a bad idea either.