Interoperability between declarative and imperative paradigms via PA

The goal of this post is to describe the problem of interoperability of different paradigms and propose the short- and long- term solution that involves minimal changes but preserves maximum generality.

Declarative vs imperative

Dividing the execution flow into two parts, we can talk about high-level, specification layer and low-level, execution layer. The execution layer always operates in the imperative paradigm - a specified sequence of commands is executed. In contrast, a declarative paradigm doesn’t require specifying how to do something but only what the start and end states are. The specification layer can be described in both imperative and declarative ways. The ARM operates in declarative specification paradigm. Ethereum and most other blockchains operate in imperative specification paradigm.

Going from imperative specification to imperative execution layer is fairly straightforward: for example, if I say “buy me a red apple from the Rewe nearby”, the execution steps include getting out of the house, going to Rewe, buying an apple, going home, etc.

In the declarative paradigm, I just say “I want a red apple” and another actor figures out how to get it (to buy or to grow?) and where to get it from (Rewe nearby or go to Frankfurt?).

Interoperability scenario: local assets, external services

Let’s consider the following scenario: a user holding assets on Anoma wants to use an application on these assets that is hosted on another chain, for example, Ethereum (from now on we will use Ethereum as a universal example). That requires the following steps:

  1. Off-chain: construct the Anoma transaction that provides the resources and specifies the desired end state
  2. Anoma (declarative step): execute the Anoma transaction, bridging the assets to Ethereum
  3. Ethereum (imperative step[1]): Call the relevant application contracts on the provided assets

The following problem arises: the scenario initiates on the Anoma side - the side that provides the assets, but by definition, an Anoma transaction doesn’t specify how to achieve the state desired by the user. The imperative chain that hosts the application requires a sequence of commands. How do we figure it out?

Who figures out the order?

There are multiple ways. Let’s consider some:

1. The user specifies the order off-chain (during step one: off-chain)

That option is the simplest in the sense of implementation, but does take away the declarative advantage of the ARM design. Each paradigm comes with trade-offs, and if we don’t use the advantage it gives, we are only left with the downside part of the trade-off. However, this might be an acceptable solution in the aforementioned scenario where the user already intends to use an Ethereum dapp (and accepts the trade-offs of the imperative model) and only needs the Anoma part to provide the assets.

2. The solver completes the transaction with ordering information (during step one: off-chain)

This option is most fitting assumption for our (long-term) model: users specify what they want (declarative specification) and solvers figure out the path (imperative execution). However, that solution assumes a solver network (which might consist of a single solver) and solving (no pun intended) some associated safety and privacy problems. Since the solver would have to complete the transaction, the user must either trust the solver to see the transaction and modify it, but not tamper with it, which doesn’t scale, or we need to develop a mechanism that enables selective integrity - modifying the transaction in the allowed ways without modifying it in the disallowed ways. In short, it will take some time to get there.

3. A contract figures out the order (during step three: on Ethereum)

Given an Anoma transaction with multiple resources that perform external contract calls, the PA determines which calls have to be executed in which order to achieve the desired state. Generally, figuring out the order of the external calls is a non-trivial task. However, if in the generic call design we expect the same pattern (move assets - perform various external calls - move new assets back), the task becomes way simpler.

It might be unwise, however, to put this job onto the PA, since the generic call application, even though being called generic, still enables only a subset of what can be done via the protocol adapter. In other words, the protocol adapter design is more generic than the generic call resource design.

On the other hand, it might still be acceptable to do that if:

  1. We don’t expect to go beyond this pattern - if GC affordances is the most we expect of the PA design in the long-term.
  2. We don’t mind including this logic in the PA temporarily until we find other affordances that do not require this step (and then move this logic elsewhere). At the moment, the GC design represents the largest surface of what can be done via the PA.

If we want to preserve architectural generality and don’t want to tweak contracts temporarily, it might be wiser to introduce an intermediate step contract that is responsible for ordering of actions that include GC resources. That, of course, is in conflict with the long-term goal of FP since it is based on the fact that both the PA and contracts can tell the difference between GC resources, asset provisioning resources, and other kinds of resources.

What to actually do?

Having said all that, I propose to move forward with the first option for now and the second in the long-term. The first option is the simplest but still requires changes.

1. Make execution on PA deterministically ordered

The PA will have to check for the provided ordering information in the payload and execute calls in the specified order. For the transactions where order doesn’t matter, a random order should be picked to avoid ordering information leakage - the observer won’t be able to tell if the order is picked or assigned randomly.

2. Lift payload constraints in the TTC circuit

At the moment we strictly constrain all payloads in the TTC circuit to be either empty or contain information for wrap/unwrap. We will have to (partially) lift this requirement in the circuit to allow users add ordering information. It shouldn’t introduce any harmful scenarios since by definition any order should be safe in the declarative paradigm as long as it leads to the desired outcome.

The ordering information should be included in external payload since it is..external. So we just need to make sure this and any other external information that doesn’t need to be constrained could be included in the payload.

3. Avoid overconstraining in circuits

Longer-term supporting solution here would involve the ARM changes that would allow storing arbitrary not constrained information in a transaction / action. As long as this data cannot harm, it should be a fine enhancement.

In the short-term, however, we just need to make sure we don’t constrain the circuits in a way that leaves no space for harmless field usage. Given that we don’t have a lot of active circuits, that should be easy.

Specifically, we need to allow inclusion of external information that we don’t need to constrain but that could be helpful in other contexts (such as imperative ordering information that cannot harm).

4. About the order in the action tree root

It is true that we currently recompute the action tree root based on the order of the resources in the action. While it might turn out fragile in certain contexts in the future, I don’t actually see harm in that in the short-term if it is decoupled from execution. We just need to remember that the order of resources is neither deterministic nor meaningful.

So..why not encode it in resource order within the action?

After writing all this I asked myself the same question. Here is why.

User specifying the order is a hack we want to use in the short term, but in the long term specifying the state and specifying the execution order are decoupled from each other with the latter happening after the actions are “sealed” by the user. The user provides the resources and the desired end state, signs it (or authorizes another binding way) and hands it to the solver.
At that step, the execution order is not known but the order of resources in the action is fixed.
In the next step, the solver will figure out the optimal execution ordering and everything else for the user, add this information to the transaction, and send it further.

Dedicating a separate field for ordering and other solver information will provide not just a cleaner approach. Delegated ordering is simply not possible if the user has to sign the execution order before the solver figures it out, and signing has to happen before to ensure tampering resistance.

It might happen that assumptions will change in the future and the solver’s expected work will include more/less stuff. Either way, separating authorization from figuring out the execution order is architecturally cleaner: the user authorizes the state and the order can be added later.


  1. Anoma and Ethereum interoperate on the same level here, so both steps refer to the high-level paradigm. In the Anoma case it is obvious because high-level and low-level paradigms differ. For Ethereum, they are the same.:right_arrow_curving_left:︎

  1. Anoma and Ethereum interoperate on the same level, so both steps refer to the high-level paradigm. In the Anoma case it is obvious because high-level and low-level paradigms differ. For Ethereum, the paradigms are the same so it might be unclear when we are talking about low- or high-level. ↩︎

1 Like

Thank you for the clear write-up. I agree that we must keep the ARM declarative and with the short-term solution(s) that you propose as well as lifting payload and unnecessary constraints.

We could make ordering information optional. Calls without ordering information will be executed in their order or appearance[1] as it is done now and after the ordered calls.

Do we assume a single user or do we want to allow multiple parties to specify the order?
If the latter is the case, how can we handle conflicts in the order, i.e., a duplicated position[2]?

Let’s discuss the required changes in detail and implement them when we work on the PA v2 upgrade.


  1. that is not fixed ↩︎

  2. For example, two users wanting a call to happen first at position 0 ↩︎

I thought about this at first, but for privacy we should assign random order when the order doesn’t matter so that an observer couldn’t tell the difference between the transactions where the order matters (GC actions) and other transaction types

We can always have a fallback option of executing in the given order if no order is specified though. This way we are always safe.

I think we should assume single party specifying the order. By the time we enable multi-user situations, we should probably have solver networks and protocols for solvers specify the order (the long-term plan), so that the users won’t have to specify anything anymore

Which software component will assign the random order? I assume you are thinking about the backend transaction service currently.

Once this moves beyond having a transaction service to solving (or even consensus), the incentives of the party that does the ordering have to be considered thoroughly. A random order might not prevail if there is a possibility for more lucrative ordering. I would like to understand your thoughts about privacy and ordering in more detail here.

I think we can do that on the frontend.

If we are talking about the long-term solving scenario, pursuing the potentially more profitable order either results in an invalid transaction or a valid transaction. If the transaction is invalid, there is no profit from executing the transaction, even partial, because the transaction is atomic and shouldn’t be executed - this is also a property of the ARM we need to make sure doesn’t break with interop[1]. So the task here is no ensure that there is no profit from failing transactions.

If the transaction is valid, it means the intents are satisfied (when we have solvers, we can assume having intents). If all intents are satisfied, there should be no harm for the users if the solver pursues the more profitable for them order (the intent should be able to capture the desired outcome)

Generally, solvers are assumed to be incentivized by the fees they receive for solving and reputation. So, there is no hard control over what they do, but if they misbehave, they will be punished. I believe, the slow game report can give us some more guidance about regulating solvers.

You may think of the transactions that are so profitable that can cancel out the irrecoverable damage for the solver’s reputation in case they pursue the attack. To take care of this, the user authorization mechanism should be proportional to the risks, so that quick and common transactions are executed seamlessly and the higher the transferred value is, the more complex the authorization mechanism becomes. I assume the user who wants to transfer large amounts or valuable items won’t mind being extra careful.

If we think of extracting value from the Ethereum side, I’m not sure how much control we have here but again this is not my area of expertise.

Solver network setup is a complex topic that we, in my opinion, need to do more research on to be able to introduce it properly. Mechanism design is not my area of expertise either, but I can say that the security and the right incentive design are very important here, even if we don’t assume any privacy.

If we consider interop scenarios like the ones introduced by GC, it becomes even more complex since the solvers may profit from exploiting the design features of the other side, e.g., Ethereum and I cannot say right now if we need to do something about it and if we can to do anything about it.

Privacy for solving is super hard and there are no good solutions for that. I explored that topic a couple of years ago and since then it didn’t change much. Luckily, it is decoupled from the settlement privacy (data on the blockchain stays private) and has different requirements (it is short-term vs long-term for settlement privacy), meaning we can consider different solutions (e.g., TEEs).

To summarise:

  • I don’t see non-trivial solver networks happening soon
  • I don’t know much about the mechanism design of solver networks
  • We need to do more research
  • Private solving is hard

  1. we make judgements and assumptions about what guarantees the ARM can provide based on its design. If we don’t implement the design fully, we cannot necessarily guarantee the same properties ↩︎

1 Like