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:
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?)@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?)with-capability
for this capability?)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.