KIP | Title | Author | Status | Type | Category | Created |
---|---|---|---|---|---|---|
7 |
Pact Module References |
Emily Pillmore emily@kadena.io |
Final |
Standard |
Pact |
2020-09-15 |
This proposal is to define and implement two new features for the Pact smart contract language as discussed in #12:
- Module references, a type which allows for Pact modules to be treated as concrete datatypes using a fully-qualified module symbol.
- A secure and limited mechanism for module-polymorphic dispatch.
Pact lacks a mechanism for runtime dispatch. All dispatch in pact is resolved statically at compile/install time, and no polymorphism is offered as a point of principle. While this is not a significant limitation for many smart contract applications, and indeed "static linking" has safety benefits, certain kinds of apps need to interact with other applications dynamically.
Consider a Uniswap-style exchange that interacts with fungible-v2
tokens. On the EVM, Uniswap offers the ability to add new pairs on-demand, as a business-critical feature. In Pact, dynamic interaction with other contracts is not possible.
A work-around could employ a mechanism, such as a "fungible registry" that would provide indirection along the lines of dispatching on token symbol to fungible-v2
methods like transfer
. A new token would simply require a modification referencing the new token's code and an update to the code on the blockchain. Such a registry could service any number of applications seeking to access fungible tokens.
Nonetheless, in addition to updating the registry, the client software, in this example the exchange, would have to be re-uploaded simply to link to the new registry version. This is because Pact intentionally does not automatically upgrade downstream dependencies, as an safety feature for code stability and resilience.
However, the registry code itself would reveal another problem: the lack of a polymorphic dispatch mechanism. The registry would have to manually case on symbol for each operation, resulting in massive duplicative calls to transfer and other methods. Code proliferates with the potential for bugs.
A solution must allow for dispatch in a module, and polymorphic access to the methods of implemented interfaces while preserving the strong security principles of the Pact ethos.
As discussed in #12 and in the motivations above, this proposal revolves around two related features:
-
A
module-ref
type for module references, so that modules can be treated as concrete datatypes using a fully-qualified module symbol. -
A secure and limited mechanism for module-polymorphic dispatch.
We propose the following syntax and semantics for module reference declarations and their resolutions:
module{ a1,a2,...,aN }
Where module a1,a2,...,aN
are N-many distinct fully-qualified interface symbols. As an example, consider the following function:
(defun add-whitelist-fungible
( fungible:module{ fungible-v2, example.whitelist-v1 } )
)
The function add-whitelist-fungible
would be allowed to take as an input any fully-qualified module symbol that implements both fungible-v2
, and example.whitelist-v1
. Semantically, two changes are needed for this to happen: the introduction of a new parsing form allowing for the reuse of the module
keyword in any type position within a function declaration, and an addition to Pact's type system allowing for module references. These are both backwards-compatible changes, and can be done without changing the existing semantics of any pact code.
We propose the following syntax module-polymorphic dispatch:
- A module reference should be allowed to access its function and constant members by using dot-accessor notation, dispatching on the reference's implementation of that member.
As an example, consider the following code snippet:
; assuming autonomously installed (example.TRANSFER "me" "you" 1.0) capability
(defun transfer-whitelist-fungible
( fungible:module{fungible-v2, example.whitelist-v1}
sender:string
receiver:string
amount:decimal
)
(fungible.transfer sender receiver amount)
)
This code will dispatch the transfer
function of a module and apply it to the inputs sender
, receiver
and amount
. Capabilities required by the module referenced must be autonomously installed by the caller.
Note that because a module's members are required to be unique by construction, and because a module cannot implement two interfaces with conflicting members, module references will never have overlapping row entries, and so, each interface required as part of the module reference will contribute its members in a strictly disjoint additive sense.
Changes are purely additive, and will not break existing code.
See KIP discussion #12 and the above rationale section.