System Design
In the Requirements section, we identified the need for the following smart contracts: Reputation, Bench, Forum, Work, Proposal, and Rollup.
We also identified the need for a mechanism for coordinating Rollup Post validation. To address this need, we introduce Matrix, a federated messaging protocol. Our application uses the Client-Server protocol to communicate with a Matrix Homeserver.
Contracts
The following is a dependency graph, where the arrows represent one contract calling another.
flowchart BT
subgraph Core Contracts
Reputation
Bench
Forum
end
subgraph Business Logic
Work
Proposal
Rollup
end
Bench --> Reputation
Proposal --> Bench
Proposal --> Reputation
Work --> Reputation
Work --> Bench
Work --> Rollup
Work --> Proposal
Forum --> Reputation
Bench --> Forum
Rollup --> Bench
Reputation, Forum, and Bench can be considered core contracts. Together they provide the necessary primitives for a DAO to carry out its business.
Work, Proposal, and Rollup can be considered business logic. They allow the DAO to support arbitrarily complex use cases, according to its needs.
Future work may include utilizing the process defined by the Proposal contract to govern features of the core contracts themselves.
Reputation
We can achieve the requirements of the Reputation contract as follows:
-
Reputation may be implemented as an ERC20 token contract.
-
transfer
andtransferFrom
methods must be disabled, so that REP may not be transferred. -
Reputation internal methods (
mint
,burn
,update
) may be called only by the Bench and Forum contracts.
Bench
To achieve the Bench requirements, the contract must do the following:
-
Define a minimum quorum
-
Define a minimum and maximum VP duration
-
Keep a record of Validation Pools
-
Implement a method for initiating a Validation Pool (VP)
-
This should be a payable contract call. The value transferred is the fee for this VP.
-
The VP shall mint an amount of REP equal to
valueTransferred * mintingRatio
mintingRatio
may be set statically, or may be taken as a parameter when initiating a VP. TakingmintingRatio
as a parameter enables greater flexibility, but increases overhead cost per transaction. This makesmintingRatio
a good candidate for being governed by a Proposal process, outlined in Future Work.
-
Half of the minted REP shall be staked in favor of the VP, and the other half shall be staked against the VP.
- This ratio could be taken as a parameter or governed by a Proposal to increase the flexibility of the DAO; see Future Work.
-
-
Implement a method for receiving REP stakes for/against the VP
-
Stake should be rejected if the VP
duration
has already elapsed. -
Staking REP means that a DAO member makes an allowance for the given amount of REP to be withdrawn from their balance. The amount of the shall be recorded along with the boolean
inFavor
, but the REP is not to be withdraws from the sender's balance until certain conditions are met during the evaluation of the VP outcome (explained below).
-
-
Implement a method for evaluating the outcome of a Validation Pool
-
Outcome may not be evaluated until VP
duration
has elapsed- An exception can be made if
totalStakedFor + totalStakedAgainst == totalSupply
. In thise case the DAO is all-in, and there's no reason to wait before evaluating the outcome.
- An exception can be made if
-
The quorum is met if
(totalStakedFor + totalStakedAgainst) * quorum[denominator] >= totalSupply * quorum[numerator]
-
The vote passes if
totalStakedFor * winRatio[denominator] >= (totalStakedFor + totalStakedAgainst) * winRatio[numerator]
-
If the vote passes and the quorum is met,
-
If
redistributeLosingStakes
is true, then(bindingPercent / 100) * totalStakedAgainst
shall be distributed among the accounts who staked against the VP, in proportion to their stakes. -
The REP that was staked in favor of the VP during its initiation, shall be transferred to the Target Post, and propagated via the Forum Reference mechanism to the authors of the Post and its referenced Posts.
-
-
If provided,
callbackOnValidate
should be executed, and should be passed the results of the VP.
-
totalSupply
refers to the total amount of REP held by all DAO members.
InitiateValidationPool
Parameters
Name | Type |
---|---|
postId |
string identifying the Target Post |
duration |
positive integer number of seconds |
quorum |
integer numerator, integer denominator |
winRatio |
integer numerator, integer denominator |
bindingPercent |
integer |
redistributeLosingStakes |
boolean |
callbackOnValidate |
function(ValidationPoolResult , callbackData ) |
callbackData |
bytes |
ValdiationPoolResult
Fields
Name | Type |
---|---|
sender |
wallet address |
votePasses |
boolean |
quorumMet |
boolean |
Test Cases
A set of test cases for the Bench contract can be found in the Solidity prototype implementation.
Forum
To achieve the Forum requirements, the contract must do the following:
-
Keep a record of Posts, indexed by Post ID
-
Implement a method to add a Post
-
Implement a recursive method to distribute Reputation to a Post's references and authors.
- This method must be called only by the Bench, when a Validation Pool is accepted.
AddPost
Parameters
Name | Type |
---|---|
postId |
string |
authors |
Array<AuthorWeight > |
references |
Array<ReferenceWeight > |
AuthorWeight
is an object or tuple with the following fields:
Name | Type |
---|---|
authorAddress |
wallet address |
weightPPM |
integer |
ReferenceWeight
is an object or tuple with the following fields:
Name | Type |
---|---|
targetPostId |
string |
weightPPM |
integer |
PropagateReputation
Parameters
Name | Type |
---|---|
postId |
string |
amount |
integer |
initialNegative |
boolean |
depth |
integer |
PropagateReputation
Logic
-
PropagateReputation
is a recursive function. -
The recursion is initiated by a Validation Pool targeting a given Post.
-
Each time
PropagateReputation
is called,depth
should be incremented. -
Negative references "leach" reputation from the target, while positive references "donate" reputation. A Post may include both negative and positive references. Negative references should be processed first, so that a Post may then donate the leached reputation.
-
There are cases where reputation effects are unable to be propagated, due to the following constraints:
- Post and Author reputations may not drop below zero.
- A negative reference may at most undo the effects of prior posts, but not reverse them.
depth
may not exceeddepthLimit
(a.k.a. Reference Chain Limit).
PropagateReputation
should therefore return the amount that was unable to be propagated. We refer to this as a "refund". Subtracting this refund amount from the attempted amount gives the actual amount propagated.
Test Cases
A set of test cases for the Forum contract can be found in the Solidity prototype implementation.
Proposals
To achieve the Proposals requirements, the contract must do the following:
-
Define an attestation threshold
-
Keep a record of Proposals
-
Implement a method to initiate a Proposal
- The caller should be able to provide an optional callback to be executed when and if the Proposal is accepted. This can be used, for example, to update parameter values of a smart contract.
-
Implement a method for a DAO member to attest to a Proposal
-
Implement a method to evaluate the attestation for a given Proposal
- If the attestation threshold is met, begin the referendum process
-
Implement the referendum process
-
Initiate the VP for the current stage referendum
-
Provide a callback to be executed when each referendum VP concludes, that advances the Proposal through the referenda stages according to the requirements.
-
If the Proposal passes all referenda stages, and a callback was provided, the callback should be executed.
-
Availability
For convenience, we can define an Availability contract, as a base contract that other contracts may extend. Work contracts as well as the Rollup contract need to use this Availability mechanism.
To achieve the requirements, the Availability contract must do the following:
-
Keep a record of Worker availability stakes
-
Implement a method to accept Worker availability stakes
- If a Worker submits multiple availability stakes, they may be combined into a single availability stake.
-
Implement a method to select a Worker by random weighted selection, weighted by the amount each Worker staked.
StakeAvailability
Parameters
Name | Type |
---|---|
amount |
integer |
duration |
integer |
Availability Stake
Properties
Name | Type |
---|---|
worker |
wallet address |
amount |
integer |
endTime |
integer |
assigned |
boolean |
Work
To achieve the Work Smart Contract requirements, a work contract must do the following:
-
Inherit from the Availability contract
-
Define a price for the work
- Optionally, implement a method to initate a Proposal to change the price
-
Implement a method for a Customer to request work
- This should be a payable method
-
Implement a method for a Worker to submit Work Evidence (WEV)
-
Implement a method for a Customer to submit work approval/disapproval
- Once approval/disapproval is submitted, either initiate a Validation Pool tarageting the WEV, or submit the fee and worker's REP stakes to the Rollup contract instead (explained below).
Onboarding
The process of adding a new Member to the DAO can be implemented as a Work contract. The Customer is a prospective Member. A work request is a request to be onboarded, and the assigned Worker is responsible for reviewing the onboarding request.
The reviewer submits their review as work evidence. The Onboarding contract initiates a VP targeting this review, including a portion of the fee submitted with the onboarding request. If this VP is approved, then the Onboarding contract initiates a second VP, this time targeting the onboarding request, including the remainder of the fee.
Rollup
Rather than submit every Post on-chain and conduct every Validation Pool on-chain, it is more efficient to keep a collection of Posts off-chain, and add a single Rollup Post on-chain representing multiple off-chain Posts.
With this Rollup Post, we have the opportunity to attribute credit to multiple authors, with a weight assigned to each author.
The Rollup Post should weight authorship in accordance with the off-chain Validation Pools that have taken place.
To achieve the Rollup requirements, the contract must do the following:
-
Inherit from the Availability contract
-
Keep a queue of items to be batched
-
Implement a method to add an item to the queue of items to be batched
-
This should be a payable method, because it needs to receive the fee that would have been sent to initiate a Validation Pool directly targeting the given Post.
-
The batch item should reference a Forum Post ID. However, the Post itself does NOT need to be stored on-chain. It is sufficient for the Post to be available off-chain, as long as the Batch Worker and Validating Clients are able to retrieve it.
-
-
Implement a method for the current Batch Worker to initiate a Validation Pool targeting a Rollup Post that represents items from the batch queue.
-
Items must be submitted in order without skipping any items
-
When initiating the VP targeting the Batch Post, the Rollup contract should include the aggregate of the fees that were included with each item in the batch.
-
-
Implement a method to select a new Batch Worker
- If this method is called to replace a Batch Worker who has failed to submit the next batch, a Validation Pool should be initiated and the Batch Worker's stakes submitted in favor of the VP. The DAO members may then stake against this VP, punishing the worker who failed to submit the batch. This is a case where a Validation Pool would be initiated without a fee, and no Reputation would be minted for that VP.
AddItem
Parameters
Name | Type |
---|---|
author |
wallet address |
postId |
string |
stakeAmount |
integer |
Batch Item
Properties
Name | Type |
---|---|
sender |
contract address |
worker |
wallet address |
postId |
string |
stakeAmount |
integer |
fee |
integer |
SubmitBatch
Parameters
Name | Type |
---|---|
batchPostId |
string |
batchItems |
Array<string > |
poolDuration |
integer |
SubmitBatch
Logic
-
A batch may only be submitted by the currently assigned Batch Worker, unless there is no currently assigned Batch Worker.
-
batchItems
is an array of Post IDs that are included in the batch.-
Submitted batch length must not exceed the length of the current batch queue.
-
Each Post ID in the submitted batch must match, in order, the Post IDs in the batch queue, up to the length of the submitted batch.
-
-
A Validation Pool should be initiated targeting the
BatchPostId
.- The fee included when initiating this VP should be the sum of the fees for each item included in the submitted batch.
-
For each included item,
stakeAmount
should be staked in favor of the VP on behalf of the Worker that submitted the item. -
The availability stake of the current Batch Worker should be staked in favor of the VP.
-
The submitted items should be removed from the batch queue.
-
A new Batch Worker should be assigned.
Automated Staking
To achieve the Autonomy requirement for the DAO, Validation Pools targeting Work Evidence Posts or Rollup Posts should be automatically validated, without human intervention. This means the rules for validating a given VP and its target Post must be represented in code.
Our prototype demonstrates the simplest possible type of rule that can be applied: a Work Evidence Post is considered valid if it starts with a specific string, "This is a work evidence post". Another type of evidence we can consider is signatures from the customer, from the worker, or from reviewers.
Validation Pool
The client that each worker operates should be prepared to render a decision on each Executive Validation Pool within its specified duration. For example, if a Work contract submits a VP targeting a particular Work Evidence Post, the Worker client should evaluate this post and determine if it qualifies as valid Work Evidence according to the rules of the DAO.
Rollup
Each Worker client should compute the expected values for the Batch Post's authors and references, based on the outcomes of the Matrix Pools corresponding to each item in the batch. If the submitted Batch Post matches the expectation, the Worker client should stake in favor of the Batch Post VP. Otherwise, it should stake against the VP.
Off-chain Operations
As outlined in the Rollup section above, we need to define processes for handling off-chain Posts and Validation Pools. Posts are represented by a unique identifier on-chain, but the Post content is stored off-chain. So, every on-chain Post must have a corresponding off-chain Post. These off-chain posts should be visible to the public. To achieve this, we introduce a Forum API, for supporting web clients in reading and writing posts. We use Matrix as the off-chain forum database. Each Forum API instance will read the history and receive new posts using the Matrix client-server protocol.
Matrix implements a layer of encryption and identity management. We define an identity registration process, whereby a Matrix user may send a signed message asserting their wallet address. Messages from this Matrix user can then be attributed to their wallet address, and their on-chain REP can be verified as needed.
As mentioned above, Matrix serves as the Forum database. For this we use io.dgov.forum.post
events. We can also use Matrix to conduct off-chain Validation Pools. For this we use io.dgov.pool.start
, io.dgov.pool.stake
, and io.dgov.pool.result
events.
Forum API
Write
Parameters
Name | Type |
---|---|
sender |
Wallet address |
authors |
Array of tuples: (Wallet address, weight) |
content |
String |
references |
Array of tuples: (Post ID, weight) |
embeddedData |
Object |
signature |
Sender or author signature of content and embeddedData |
In order to protect the integrity of the off-chain Forum, the API should verify that the Post is signed by one of its authors, or by the sender. The reason for allowing the Post to be signed by the sender rather than by an author, is to support the use case of adding a Post on behalf of its author(s).
The API should compute a hash of all input parameters except for references
, and use this hash as the key for storing the Post. The hash should also be returned to the caller.
The reason for excluding references
from the hash, is to support the use case of importing Posts from an existing data source. If we included the references, then to import any Posts from an existing data source, we would have to import every referenced Post, starting with the earliest, in order to compute the entire tree of references made by a given Post. By omitting references from the hash, it becomes possible to precompute the hash (a.k.a. ID) of referenced Posts that have not yet been imported.
The reason for excluding references
from the signature, is to reduce the number of queries that must be made to an existing data source when importing a Post. (Specifically, when importing from the Semantic Scholar API.) If we had to include the references in the signature, there is an "N + 1 problem" to the query pattern. For each paper, we would need to perform an additional query for each of its references. A single query can't ask for the references of the references of a paper.
Note that because references
is not included in the hash or signature, there is a replay attack vulnerability. Someone could read an existing Post, modify the references
, and write the modified Post back to the API. The signatures will still be valid even though the references have changed, and the new references will overwrite the previous references. Note that this would only affect the off-chain record of the Post's references. If the Post is published to the on-chain Forum, it is not subject to such modification, as a Post with a given ID can only be added once. To mitigate this vulnerabliity in the off-chain Forum, we should reject a write attempt if a Post with the given ID already exists.
When a Post is written, the Forum API should send an io.dgov.forum.post
event to the Matrix room. Peer nodes should listen for events of this type from other peers. When an io.dgov.forum.post
event is received, the recipient should write the Post to storage just as they would if the write
endpoint were called directly.
Each Forum API node should keep track of the most recent io.dgov.forum.post
event received. On startup, each Forum API node should query the Matrix Homeserver for any newer io.dgov.forum.post
events.
Read
The read
endpoint should accept a Post ID argument, and retrieve the corresponding Post from storage. Before sending the Post to the caller, the Forum API should verify the hash and signature, to ensure the integrity of the record.
Matrix Pools
We can use Matrix events to carry out a variation of a Validation Pool, which we call a Matrix Pool. The main difference is that we don't have a native fungible currency in Matrix. Therefore Matrix Pools do not require a fee, and do not mint Reputation.
In principle, we could define rules governing a Chat REP, that is distinct from our on-chain REP. However, the rules for governing such a Chat REP are not obvious. It would have the fundamental challenge that there is nothing truly at stake, since Chat REP would not be backed by fungible currency. Therefor we defer Chat REP for future work.
Since Matrix Pools have no direct power over on-chain Reputation, Matrix Pools are always non-binding, meaning that Members who stake on the losing side of a Matrix Pool do not lose the staked REP. This is in contrast to on-chain Validation Pools, which may be anywhere from 0 to 100% binding.
The main use case for Matrix Pools is in the context of the Rollup process. Rather than initiate an on-chain VP, a contract may add an item to the Rollup batch queue. The Rollup Batch Worker initiates a Matrix Pool for each of these items. Then, when the batch interval has elapsed, the Batch Worker submits a Batch Post whose authorship is determined by the outcomes of the corresponding Matrix Pools for the included batch items.
Like on-chain VPs, Matrix Pools have three operations: Initiate Pool, Stake, and Evaluate Outcome. All Worker clients which intend to participate in the on-chain VP targeting the next Batch Post should listen for and handle these messages.
Initiate
To initiate a Matrix Pool, a Member may send an io.dgov.pool.start
event.
io.dgov.pool.start
Properties
Name | Type |
---|---|
postId |
string |
sender |
contract address |
fee |
integer |
duration |
integer |
quorum |
[integer numerator, integer denominator] |
winRatio |
[integer numerator, integer denominator] |
Stake
To register a stake, a Member may send an io.dgov.pool.stake
event.
io.dgov.pool.stake
Properties
Name | Type |
---|---|
postId |
string |
amount |
integer |
inFavor |
boolean |
The stake should be accepted only if the following conditions are met:
-
It is sent before the Matrix Pool duration has elapsed.
-
The Member who sends the stake has an on-chain REP balance greater than or equal to the staked amount.
Evaluate Outcome
When the Matrix Pool duration has elapsed, the current Batch Worker should evaluate the outcome and send an io.dgov.pool.result
event.
io.dgov.pool.result
Properties
Name | Type |
---|---|
postId |
string |
result |
Result object |
Result
Properties
Name | Type |
---|---|
stakedFor |
integer |
stakedAgainst |
integer |
totalSupply |
integer |
votePasses |
integer |
quorumMet |
integer |
This message provides an opportunity for other Workers to compute their own expected result for the Matrix Pool and verify the result provided by the Batch Worker. If there is a discrepancy, it can be logged and reported for investigation.
Register Identity
A client may send an io.dgov.identity.register
event via Matrix.
-
This event should include a message signed by the sender's wallet.
- To prevent replay attacks, the signed message should include the Matrix user ID of the sender.
-
All Forum nodes should receive this message and do the following:
-
Verify the signature, extracting the sender's wallet address
-
Verify that the signed message contains the sender's Matrix user ID
-
Store a record associating the given Matrix user ID with the given wallet address
-
Once this has been performed, messages from the given Matrix user can be attributed to the given wallet address.
User Interface
Web App
Widget
Other
- Imprt from Semantic Scholar
- Import from Matrix
- Bot Commands