Further brainstorming on several AVM-related topics

The objective of this topic is to conduct brainstorming, explicate rough ideas, and discuss potential directions as pertain to the question of how to (a) represent and (b) compile several features that I think we will want to support in the AVM (in the near future):

  • First-class object access control permissions
  • Intents & solving
  • Object scry (search)

First-class object access control permissions

Previously I outlined a version of access control which uses external identities as principals. This captures the basic concept, but I think we actually want an even more general principal type: objects themselves. An external identity, of course, can be represented as a simple stateless object with verify and encrypt methods, and any stateless object with those methods can be represented as an external identity – so what we’re really adding here is statefulness (which can then later be re-encapsulated into functions through verification of network history, if we want).

Let’s take our labeled SimpleKudos example from before:

Now, in order to check this metadata, instead of calling the verify function on A as an external identity, we call the verify method on the A-object (whatever that is):

As far as I can tell, it’s not really any more complicated than that. Now we can easily have stateful permissions, where what is required to authorize a particular action may change depending on what else happens in the transaction (e.g. maybe no more than 5 tokens can be transferred within a single hour), and encryption could be similarly stateful (perhaps, for compliance reasons, transfers of amount > 5 must be additionally encrypted to a third party).

TODO: Should there be an isomorphism between calling verify and another object itself sending a message? Consider, for example, how the “A balance” object here is only supposed to accept calls from the “SimpleKudos” object – should it, too, be calling “verify”? I think we want both options, but we should figure out when they are equivalent and when they aren’t.

TODO 2: Similarly, there’s some potential isomorphism between encrypting data to an identity and sending a message to an object (which, in our theoretical model, only that object should be able to read). It seems worthwhile to figure out exactly what this correspondence looks like.

Intents & solving

I think that our aim here should be to represent intents and solving in the AVM in a way which:

  1. Allows for new types of intents to be built on top of existing applications (as opposed to requiring that the application include all intent types up front), and
  2. Allows for types of intents which are unaware of each other to be matched (assuming that the actual constraints can be satisfied)

Both of these requirements are largerly in service of allowing intents to be declared “at runtime” by the user [at least in principle], as opposed to up front by the application developer, and having the appropriate kinds of “structural matching” so that two users who do not communicate with each other (declaring their own “intent types”) can still have their intents matched if the actual structural constraints are satisfied.

Just as a note, a previous object intent model modeled a swap intent as part of the SimpleKudos application, which is an approach that does not satisfy these requirements.

Let’s start with our classic SimpleKudos example (with access control, which we will need):

Suppose that we now want to swap kudos. With first-class access control, we can now represent a swap as a transfer to an intent object, which will release the swapper’s resources only if an appropriate kudo (of another type) is provided. How might we represent this? Perhaps like so:

This diagram likely merits some explanation:

  1. First, we have a top-level ephemeral object, which:
    a. Creates an ephemeral intent object which will temporarily own the kudos.
    b. Transfers the kudos from A (the originating user) to the intent object.
  2. Second, we have an ephemeral intent object. The ephemeral intent object:
    a. Calls the global solver object to find a counterparty and the amount of “Kudos 2” that A will ultimately receive, with the constraint that the amount is greater than or equal to 9, and a preference for more if possible.
    b. Requires that the counterparty transfer the amount of SimpleKudos2 to the intent object.
    c. Transfers the amount (of SimpleKudos2) from the intent object to A.
    d. Transfers 6 (of SimpleKudos) from the intent object to the counterparty.

There are two mysterious parts here: this “require” and the global solver object – bracket these for now, explication may be found further below. Note here how the “swap intent” is built on top of SimpleKudos, which need only worry about its own transfer logic and can remain blissfully unaware of what intents might be in play.

Now, what about matching? Let’s assume that we have another user B who wants the opposite:

This is the same as before, just with the kudos denominations and amounts reversed. Now, what does it mean to match these intents? Let’s superimpose the diagrams:

Now, “all” the magic global solver object needs to do is to return the appropriate amounts and object identities (of the two ephemeral intent objects) to each other, the requirements will be satisfied, and the intents can be matched (ultimately forming a full transaction). Note here that neither intent object needed to be aware of the other (other than requiring it to send a particular message) – one could have applied some other constraints (e.g. checking the identity of the counterparty), and the other need not have noticed (as long as the constraints were satisfied).

So, what is this “global solver object”? Just an abstraction – messages to which can be compiled out (w/hierarchical object compilation) and turned into constraints to be satisfied (which actual solvers can then use to search). The appropriate constraints should be able to be found by compiling down the object abstractions with the outputs of the global solver object set as free variables (“counterparty” and “amount” in this case). Of course, whether two intents are actually matched will indeed depend on their simultaneous presence in a single solver, which matches this “global solver object” semantics quite nicely.

What about “require” (requiring that another object send a message)? I think this is just a potentially compelling AVM-level representation of multiparty transactions – in this case, we want to check that someone sent a transfer to us. Similar to the global solver object, these constraints should simply be compiled down. Note that the semantics here should be exclusive (linear) – a particular message can satisfy only one requirement (relevant discussion).

We could also consider an even more general “become_object” or “act_as_object” context, which preserves the execution context but changes the object identity [interestingly, a somewhat inverted version of the EVM’s DELEGATECALL]. This could have applications beyond just intents (e.g. seeing the view another object would have) and might be worth investigating further.

At a high level, I think that this method of representing intents at the AVM level achieves the high-level goals stated above, but obviously we need to work out all the details of how compilation would work, check that there’s enough structure for solvers to search efficiently, and see what kinds of optimizations are possible. In particular, one topic I haven’t addressed clearly here is the n-party case (e.g. A-for-B, B-for-C, C-for-A, combining amounts, etc.) – I think it should work just by having an extra intent object which first receives all the kudos, then transfers them all out – but we should work out the details here and ensure that this is suitably general (as desired).

Object scry (search)

How do we find objects? Simple: we scry.

Terminological note: I have elected to borrow the name “scry” from Anockma (more details here) – this abstraction is not identical, but related. I’m open to different terms if this overlap is confusing.

The global scry object accepts queries and returns objects (by version identifier) which match them. Queries may include:

  • Querying by identifier – finding the current version of an object with a particular identifier – prototypically, finding the current version of a known counter.
  • Querying by read-only method results – finding one, some, or all objects (within a particular scope) whose read-only methods return a certain result – prototypically, finding all kudos of denomination D owned by user Alice, or finding enough to make 5.
  • Querying by external interface – finding one, some, or all objects (within a particular scope) which support a particular method interface (perhaps even with properties) – prototypically, finding all objects which implement the correct kudos semantics. This is an advanced topic and probably better to class as future research for now [related: application standards].

The global scry object is a pure, stateless abstraction – queries must be compiled to actual SCRY opcode usage (in a transaction function or in the local domain), appropriate indexed search (e.g. for kudos), structured lookups (as described in the State Architecture ART), etc.

Lots more to be worked out on these three topics, but I’ll leave matters here for today. Thoughts and questions welcome alike!

1 Like