pact-lang

What are the semantics of capability manager functions in Pact?


The Pact documentation describes two flavors of capability: unmanaged and managed. Managed capabilities are dynamic and can change the state of a capability as it is brought in and out of scope. A managed capability accomplishes this dynamic behavior with a dedicated capability manager function. The Pact documentation has a short section on manager functions which includes an example of the TRANSFER_mgr manager function.

This documentation is a high-level summary that demonstrates why you would want to use a manager function. However, there is no entry in the Pact documentation on the @managed metadata field or the semantics of the manager function. I am struggling to see how they relate to one another and what arguments each can & should take.

For example, the TRANSFER capability uses @managed like this:

(defcap TRANSFER (sender receiver amount:decimal)
  @managed amount TRANSFER_mgr
  ...)

Which implies, to me, that TRANSFER_mgr will be called with the amount as an argument. But the definition of TRANSFER_mgr has two parameters:

(defun TRANSFER_mgr:decimal (managed:decimal requested:decimal)

What are the semantics of the transfer manager function?

For example:


Solution

  • Just one clarification on the difference between managed and unmanaged capabilities before I answer your questions, since they are a little bit different from the picture described above:

    Capabilities are never "changed": they are only granted by with-capability. What happens with a managed capability is that, in addition to defining a capability, it also defines a "resource" that is decreased in some way whenever the associated capability is granted.

    You can think of it like this: stateless capabilities are granted by with-capability and demanded by require-capability; managed capabilities setup an initial resource by install-capability (which ought to have a different name, more like install-resource), deduct from the resource AND grant by with-capability, and are demanded by require-capability.

    Note how install-capability is unique to managed capabilities, while with-capability does double duty. In a way, with-capability is really two separate operations composed together in the managed case:

    ;; You write this:
    (install-capability (TRANSFER FROM TO PROVIDED))
    ...
    (with-capability (TRANSFER FROM TO REQUESTED) EXPR)
    
    ;; ----
    
    ;; But what it does internally is more like this:
    (install-capability (TRANSFER FROM TO) PROVIDED)
    ...
    (if (already-granted-p (TRANSFER FROM TO))
        EXPR
      (consume-resource (TRANSFER FROM TO) REQUESTED
        (with-capability (TRANSFER FROM TO) EXPR)))
    

    You can see here that (TRANSFER FROM TO) identifies the capability -- in both the managed and unmanaged cases. The extra parameter relating to the resource is what's new in the managed case. The fact that it gets passed as an argument in (TRANSFER FROM TO AMOUNT) to both install-capability and with-capability is just a syntactic convenience. Which brings us directly to your questions...

    Where do the parameters come from in TRANSFER_mgr? (Is managed first the amount provided via @managed and then the result of prior calls to TRANSFER_mgr? Is requested the amount provided in calls to with-capability in the module code?)

    The @managed keyword identifies the argument referring to the resource parameter. In the case of TRANSFER, this is the amount argument, as declared by:

    @managed amount TRANSFER_mgr
    

    This also states that TRANSFER_mgr will receive two arguments related to the amount: The first being the current amount of the resource, and the second being the proposed amount to be deducted by the call to with.

    (defun TRANSFER_mgr:decimal (current:decimal requested:decimal)
    

    For install-capability, the amount argument passed is the initial amount of the resource. For with-capability, the amount argument is the amount of resource being requested before the capability can be granted. In that case, the current amount that is passed as the first argument to the management function comes from the current state of the Pact evaluator.

    How many arguments can @managed take? (Is it a single argument, or can there be many? If many, how does that affect the parameters that the manager function takes?)

    @managed allows for only a single argument, although that argument could be a list or an object. For example, you could provide a list of names as the "resource", and write a management functions that removes names from the list as they are "used"; or the resource could be an object, if you wanted a single managed capability to manage multiple resources.

    In practice, however, the managed capability feature is used mainly by coin contracts to govern transfer amounts.

    When is the manager function invoked? (Is it on calls to with-capability for this capability?)

    The manager function has the job of both confirming that sufficient resource exists, and deducting from the resource. It is called whenever with-capability is used and the capability has not yet been granted.