|
@@ -0,0 +1,115 @@
|
|
|
|
|
+# DEP 0002: Smart Contract Composability
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+status: draft
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Current Situation
|
|
|
|
|
+
|
|
|
|
|
+When creating the DAO, we needed to invent the concept of protocol owned liquidity in DarkFi.
|
|
|
|
|
+Without this, in order to have on chain DAO treasuries, the DarkFi blockchain would have to
|
|
|
|
|
+recognize funds held by both the money and DAO contracts as valid. This introduces a security
|
|
|
|
|
+risk if there is an error in the DAO contracts.
|
|
|
|
|
+
|
|
|
|
|
+Additionally it means that liquidity could only be held by the DAO contract, and any liquidity
|
|
|
|
|
+held by other contracts would have to be recognized by the consensus as valid. This would
|
|
|
|
|
+restrict the protocols that could work with on chain liquidity to a small hardcoded subset
|
|
|
|
|
+due to security.
|
|
|
|
|
+
|
|
|
|
|
+Motivated by the desire to enable protocol owned liquidity, we created the concept in
|
|
|
|
|
+`money::transfer()` of the `spend_hook`.
|
|
|
|
|
+
|
|
|
|
|
+Firstly a quick recap of how `money::transfer()` works. During the mint phase of creating
|
|
|
|
|
+coins, we construct a coin `C = hash(…, spend_hook, user_data)`. The `…` contains coin data such
|
|
|
|
|
+as value, token ID and other attributes. During the burn phase we produce a deterministic
|
|
|
|
|
+unlinkable nullifier.
|
|
|
|
|
+
|
|
|
|
|
+This is a more general ZK concept of committing to several attributes, and then later either
|
|
|
|
|
+full on revealing them or more specifically applying constraints to the attributes.
|
|
|
|
|
+To enable protocol owned liquidity, we introduced the coin attributes `spend_hook` and
|
|
|
|
|
+`user_data`, motivated by these desires:
|
|
|
|
|
+
|
|
|
|
|
+* Generalize protocol-owned liquidity enabling any third party to write contracts that
|
|
|
|
|
+ own liquidity.
|
|
|
|
|
+* Stronger security model for on chain liquidity by only depending on the money contract
|
|
|
|
|
+ when composed with contracts like the DAO.
|
|
|
|
|
+
|
|
|
|
|
+When a coin is spent, the `spend_hook` is revealed publicly. The `money::transfer()` call
|
|
|
|
|
+enforces that the subsequent contract called in the tx matches the `spend_hook`.
|
|
|
|
|
+In our example, `spend_hook = DAO`, and then our tx will have two calls:
|
|
|
|
|
+`[money::transfer(), DAO::exec()]`. When spending a coin where the `spend_hook = DAO`,
|
|
|
|
|
+then `money::transfer()` will check the next contract in the tx will match the `spend_hook`.
|
|
|
|
|
+
|
|
|
|
|
+Now you might ask some questions:
|
|
|
|
|
+
|
|
|
|
|
+* Here we are listing `DAO`, but actually we need a stricter check that the call is `DAO::exec()`
|
|
|
|
|
+ and not some other DAO method call.
|
|
|
|
|
+* We need to enforce which DAO we are operating on.
|
|
|
|
|
+
|
|
|
|
|
+This is where the `user_data` is used. We can commit to several things including which function
|
|
|
|
|
+is called in the contract. Since in the DAO, only `DAO::exec()` can be composed, we just sidestep
|
|
|
|
|
+this and enforce that when a tx has two calls, then the DAO one must be `DAO::exec()`. We then
|
|
|
|
|
+use the `user_data` to store the DAO bulla.
|
|
|
|
|
+
|
|
|
|
|
+## Motivation: Limitations of Current Approach
|
|
|
|
|
+
|
|
|
|
|
+The current approach enables contracts to own liquidity, which is how we can have DAO on chain
|
|
|
|
|
+treasuries. We have the ability for contracts to directly call other contracts. However this
|
|
|
|
|
+calling mechanism is static.
|
|
|
|
|
+
|
|
|
|
|
+We desire now to generalize the DAO calling mechanism, so any contract could be called.
|
|
|
|
|
+Currently `DAO::exec()` deserializes the `money::transfer()` calldata, and then enforces its
|
|
|
|
|
+checks on it inside wasm. These checks are hardcoded.
|
|
|
|
|
+
|
|
|
|
|
+It would be very useful if instead this data or code were to be dynamic. Therefore a DAO
|
|
|
|
|
+proposal could be called, not to call `money::transfer()` but instead to call another contract.
|
|
|
|
|
+This system would then be generic and usable with other contracts, such as an algorithmic
|
|
|
|
|
+streaming contract making calls on a ZK NFT.
|
|
|
|
|
+
|
|
|
|
|
+## Proposal: Introspective Params
|
|
|
|
|
+
|
|
|
|
|
+The current `ContractCall` struct looks like:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+pub struct ContractCall {
|
|
|
|
|
+ /// ID of the contract invoked
|
|
|
|
|
+ pub contract_id: ContractId,
|
|
|
|
|
+ /// Call data passed to the contract
|
|
|
|
|
+ pub data: Vec<u8>,
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+We propose to change the `data` field to this:
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+pub struct ContractCall {
|
|
|
|
|
+ /// ID of the contract invoked
|
|
|
|
|
+ pub contract_id: ContractId,
|
|
|
|
|
+ /// Named call data passed to the contract
|
|
|
|
|
+ pub data: HashMap<String, Vec<u8>>,
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+This way contracts can query each other's calldata in a dynamic compatible way.
|
|
|
|
|
+Some part of the params for a contract may be specific to that contract. Another part
|
|
|
|
|
+might be generic, which shares the same struct with multiple other contracts.
|
|
|
|
|
+This enables contracts to query an interface from another contract's calldata,
|
|
|
|
|
+deserialize that data and work with it, without having to hardcode a dependency,
|
|
|
|
|
+e.g `DAO::exec()` hardcoding a dependency on `money::transfer()` params.
|
|
|
|
|
+
|
|
|
|
|
+## Note on Auth Modules
|
|
|
|
|
+
|
|
|
|
|
+An alternative approach is introducing the concept of auth modules. So for example, with
|
|
|
|
|
+the DAO, a user could deploy their own contract on chain with specific logic, then
|
|
|
|
|
+make a proposal to execute that contract. We could also supply our own auth module with
|
|
|
|
|
+hardcoded branching support for several common contract types.
|
|
|
|
|
+
|
|
|
|
|
+However while this may be desirable in some cases where complex logic in DAO proposals
|
|
|
|
|
+are required, it presents several downsides:
|
|
|
|
|
+
|
|
|
|
|
+* The supplied auth module will hardcode support for a few contract types and not be
|
|
|
|
|
+ properly generic.
|
|
|
|
|
+* User deployed contracts could be expensive and error prone.
|
|
|
|
|
+* For efficiency the DAO would probably end up hardcoding support for several contract
|
|
|
|
|
+ types directly, as well as other composable contracts.
|
|
|
|
|
+
|