add spec on vesting, proposals

This commit is contained in:
Emily Martins 2022-01-19 18:09:44 +01:00
parent 1b52a2a179
commit e4670d6ee7
6 changed files with 318 additions and 2 deletions

View file

@ -15,4 +15,5 @@ Agora makes extensive use of plutarch. So it's prerequisite for understanding th
The following is a list of words/shorthands that are frequently used:
- **Governance Token (GT)**: The token that holds value within the protocol and is used for voting, rewards, etc. _Examples: Liqwid's LQ_.
- **Authority Token (GAT)**: A token that delegates authority of a particular script / token. See [tech-design/authority-token.md](./tech-design/authority-token.md)
- **Authority Token (GAT)**: A token that delegates authority of a particular script / token. See [tech-design/authority-token.md](./tech-design/authority-tokens.md)
- **Effect**: The result of a proposal, enforced by an effect script.

View file

@ -0,0 +1,64 @@
digraph "ProposalStateMachine" {
rankdir=LR;
node [shape=circle, width=1.5];
subgraph cluster_0 {
style=filled;
color=white;
"Draft phase (duration = D)" [shape=plaintext];
Draft [shape=doublecircle];
}
subgraph cluster_1 {
style=filled;
color=white;
"Voting phase (duration = V)" [shape=plaintext];
Voting;
}
subgraph cluster_2 {
style=filled;
color=white;
"Lock phase (duration = L)" [shape=plaintext];
Lock;
}
subgraph cluster_3 {
style=filled;
color=white;
"Execution phase (duration = E)" [shape=plaintext];
Execution;
}
node [shape = circle];
subgraph cluster_4 {
style=filled;
color=white;
Executed [shape=doublecircle];
}
Draft -> Voting [label="enough LQ cosigned"];
Voting -> Lock [label="proposal passed"];
Voting -> Failed [label="quorum not reached"];
Lock -> Execution [label="lock time finished"];
Execution -> Failed [label="execution not on time"];
Execution -> Executed [];
Failed [shape=doublecircle];
}

View file

@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.47.3 (0)
-->
<!-- Title: ProposalStateMachine Pages: 1 -->
<svg width="1730pt" height="274pt"
viewBox="0.00 0.00 1730.00 274.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 270)">
<title>ProposalStateMachine</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-270 1726,-270 1726,4 -4,4"/>
<g id="clust1" class="cluster">
<title>cluster_0</title>
<polygon fill="white" stroke="white" points="8,-49 8,-235 233,-235 233,-49 8,-49"/>
</g>
<g id="clust2" class="cluster">
<title>cluster_1</title>
<polygon fill="white" stroke="white" points="398,-53 398,-231 629,-231 629,-53 398,-53"/>
</g>
<g id="clust3" class="cluster">
<title>cluster_2</title>
<polygon fill="white" stroke="white" points="765,-78 765,-256 983,-256 983,-78 765,-78"/>
</g>
<g id="clust4" class="cluster">
<title>cluster_3</title>
<polygon fill="white" stroke="white" points="1148,-78 1148,-256 1406,-256 1406,-78 1148,-78"/>
</g>
<g id="clust5" class="cluster">
<title>cluster_4</title>
<polygon fill="white" stroke="white" points="1582,-126 1582,-258 1714,-258 1714,-126 1582,-126"/>
</g>
<!-- Draft phase (duration = D) -->
<g id="node1" class="node">
<title>Draft phase (duration = D)</title>
<text text-anchor="middle" x="120.5" y="-205.3" font-family="Times,serif" font-size="14.00">Draft phase (duration = D)</text>
</g>
<!-- Draft -->
<g id="node2" class="node">
<title>Draft</title>
<ellipse fill="none" stroke="black" cx="120.5" cy="-115" rx="54" ry="54"/>
<ellipse fill="none" stroke="black" cx="120.5" cy="-115" rx="58" ry="58"/>
<text text-anchor="middle" x="120.5" y="-111.3" font-family="Times,serif" font-size="14.00">Draft</text>
</g>
<!-- Voting -->
<g id="node4" class="node">
<title>Voting</title>
<ellipse fill="none" stroke="black" cx="513.5" cy="-115" rx="54" ry="54"/>
<text text-anchor="middle" x="513.5" y="-111.3" font-family="Times,serif" font-size="14.00">Voting</text>
</g>
<!-- Draft&#45;&gt;Voting -->
<g id="edge1" class="edge">
<title>Draft&#45;&gt;Voting</title>
<path fill="none" stroke="black" d="M178.77,-115C250.9,-115 374.4,-115 449.24,-115"/>
<polygon fill="black" stroke="black" points="449.47,-118.5 459.47,-115 449.47,-111.5 449.47,-118.5"/>
<text text-anchor="middle" x="315.5" y="-118.8" font-family="Times,serif" font-size="14.00">enough LQ cosigned</text>
</g>
<!-- Voting phase (duration = V) -->
<g id="node3" class="node">
<title>Voting phase (duration = V)</title>
<text text-anchor="middle" x="513.5" y="-201.3" font-family="Times,serif" font-size="14.00">Voting phase (duration = V)</text>
</g>
<!-- Lock -->
<g id="node6" class="node">
<title>Lock</title>
<ellipse fill="none" stroke="black" cx="874" cy="-140" rx="54" ry="54"/>
<text text-anchor="middle" x="874" y="-136.3" font-family="Times,serif" font-size="14.00">Lock</text>
</g>
<!-- Voting&#45;&gt;Lock -->
<g id="edge2" class="edge">
<title>Voting&#45;&gt;Lock</title>
<path fill="none" stroke="black" d="M567.4,-118.69C631.87,-123.18 740.71,-130.77 809.6,-135.58"/>
<polygon fill="black" stroke="black" points="809.67,-139.09 819.89,-136.3 810.15,-132.11 809.67,-139.09"/>
<text text-anchor="middle" x="697" y="-134.8" font-family="Times,serif" font-size="14.00">proposal passed</text>
</g>
<!-- Failed -->
<g id="node10" class="node">
<title>Failed</title>
<ellipse fill="none" stroke="black" cx="1648" cy="-58" rx="54" ry="54"/>
<ellipse fill="none" stroke="black" cx="1648" cy="-58" rx="58" ry="58"/>
<text text-anchor="middle" x="1648" y="-54.3" font-family="Times,serif" font-size="14.00">Failed</text>
</g>
<!-- Voting&#45;&gt;Failed -->
<g id="edge3" class="edge">
<title>Voting&#45;&gt;Failed</title>
<path fill="none" stroke="black" d="M566.52,-104.24C616.99,-94.33 695.87,-80.25 765,-74 1067.93,-46.61 1431.69,-52.09 1579.57,-55.92"/>
<polygon fill="black" stroke="black" points="1579.75,-59.43 1589.84,-56.19 1579.93,-52.43 1579.75,-59.43"/>
<text text-anchor="middle" x="1065.5" y="-63.8" font-family="Times,serif" font-size="14.00">quorum not reached</text>
</g>
<!-- Lock phase (duration = L) -->
<g id="node5" class="node">
<title>Lock phase (duration = L)</title>
<text text-anchor="middle" x="874" y="-226.3" font-family="Times,serif" font-size="14.00">Lock phase (duration = L)</text>
</g>
<!-- Execution -->
<g id="node8" class="node">
<title>Execution</title>
<ellipse fill="none" stroke="black" cx="1277" cy="-140" rx="54" ry="54"/>
<text text-anchor="middle" x="1277" y="-136.3" font-family="Times,serif" font-size="14.00">Execution</text>
</g>
<!-- Lock&#45;&gt;Execution -->
<g id="edge4" class="edge">
<title>Lock&#45;&gt;Execution</title>
<path fill="none" stroke="black" d="M928.25,-140C1001.75,-140 1134.1,-140 1212.61,-140"/>
<polygon fill="black" stroke="black" points="1212.85,-143.5 1222.85,-140 1212.85,-136.5 1212.85,-143.5"/>
<text text-anchor="middle" x="1065.5" y="-143.8" font-family="Times,serif" font-size="14.00">lock time finished</text>
</g>
<!-- Execution phase (duration = E) -->
<g id="node7" class="node">
<title>Execution phase (duration = E)</title>
<text text-anchor="middle" x="1277" y="-226.3" font-family="Times,serif" font-size="14.00">Execution phase (duration = E)</text>
</g>
<!-- Executed -->
<g id="node9" class="node">
<title>Executed</title>
<ellipse fill="none" stroke="black" cx="1648" cy="-192" rx="54" ry="54"/>
<ellipse fill="none" stroke="black" cx="1648" cy="-192" rx="58" ry="58"/>
<text text-anchor="middle" x="1648" y="-188.3" font-family="Times,serif" font-size="14.00">Executed</text>
</g>
<!-- Execution&#45;&gt;Executed -->
<g id="edge6" class="edge">
<title>Execution&#45;&gt;Executed</title>
<path fill="none" stroke="black" d="M1330.87,-147.45C1396.49,-156.7 1508.48,-172.48 1580.04,-182.56"/>
<polygon fill="black" stroke="black" points="1579.9,-186.08 1590.29,-184.01 1580.88,-179.15 1579.9,-186.08"/>
</g>
<!-- Execution&#45;&gt;Failed -->
<g id="edge5" class="edge">
<title>Execution&#45;&gt;Failed</title>
<path fill="none" stroke="black" d="M1330.09,-128.43C1396.02,-113.77 1509.64,-88.52 1581.42,-72.57"/>
<polygon fill="black" stroke="black" points="1582.26,-75.97 1591.26,-70.39 1580.74,-69.14 1582.26,-75.97"/>
<text text-anchor="middle" x="1494" y="-111.8" font-family="Times,serif" font-size="14.00">execution not on time</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -34,7 +34,7 @@ The components that need to be adjustable at a later point, will need to allow t
![](../diagrams/GovernanceAuthorityToken.svg)
As a result of this approach, there's a number of benefits, but also details we need to watch out for.
As a result of this approach, there's a number of benefits, but also details we need to watch out for:
#### Handling multiple effects in a single Proposal

View file

@ -0,0 +1,81 @@
# Proposals Technical Design
This document gives an overview of technical design aspects of the Proposals system for introducing, voting on and executing Governance Proposals.
| Spec Status | Impl Status | Last Revision |
|-------------|-------------|---------------|
| WIP | WIP | 2022-01-19 |
--------------------
**Spec Ownership:** [@Emily Martins]
**Authors**: [@Emily Martins]
**Impl Owner:** [@Emily Martins]
[@Emily Martins]: https://github.com/emiflake
--------------------
## Proposals
Initiating a proposal requires you to have more than a certain amount of LQ in the StakingPool (e.g. around 6-10 LQ). This is checked by consuming the proposer's stake utxo. Creating a proposal forges a proposal script state thread which is where all voting interactions happen with. Proposals are tracked in coordination with the governance, so it must be included in the transaction as well.
### Proposal voting
##### Overview of stages
A proposal fits into a state machine neatly. Draft being the starting state, and Executed or Failed being the final states. It should be noted that, for optimization reasons, not all states are exactly physical, instead, the current state is a product of the current time as well. For instance, after the voting has ended, no state transition on-chain occurs.
![](../diagrams/ProposalStateMachine.svg)
##### When is each interaction valid?
- `S`: When the proposal was created
- `D`: The length of the draft period
- `V`: The length of the voting period
- `L`: The length of the locking period
- `E`: The length of the execution period
| Action | Valid POSIXTimeRange | Valid *stored* state(s) |
|-------------------------------------|------------------------------------|-------------------------|
| Witness | [S, ∞) | * |
| Cosign | [S, S + D) | Draft |
| AdvanceProposal | [S, S + D) | Draft |
| Vote | [S + D, S + D + V) | Voting |
| Unlock | [S + D, ∞) | * |
| CountVotes (? see spec comment) | [S + D + V, S + D + V + L) | Voting |
| ExecuteProposal (if quorum reached) | [S + D + V + L, S + D + V + L + E) | Voting |
```diff
- CountVotes takes a snapshot after voting has ended, it allows users to unlock their stake without affecting votes, after the lock period has ended. This is an optional feature of locking, do we want this?
```
#### Draft phase
During the Draft phase, the proposal script has been minted. At this stage, only positive votes will count and for the proposal to get into the voting phase, a certain amount of LQ will have to be backing the proposal. The UTXO can be queried for any metadata it contains, should it contain any useful information. LQ holders can "cosign" the proposal and add their LQ in order to allow the motion to get into the voting phase.
#### Voting phase
During the voting phase a voter can submit a vote either in favour or against the proposal. This simply adds their PubKey to the list of votes as a state-machine action. By virtue of being a state-machine action, this causes contention, and may be rate-limited through some means.
##### What determines a successful proposal?
When initializing a proposal, the governance's thresholds are passed along, one of the thresholds is the `quorum`.
#### Lock phase
After the voting phase has completed, the proposal has either passed, or failed.
We wait a set time before allowing execution of the proposal's effects. This is to allow LQ holders time to prepare for the change in whichever way they seem fit. This is also a security mechanism, should the system be at risk of hostile takeover.
#### Execution phase
In the case of a failed proposal, the proposal will simply be frozen in time and never interacted with anymore. There is no value locked behind a proposal, besides the state thread itself, so nothing is lost.
In the case of a successful proposal, anyone can execute the effects, as it can only be done in a lawful way.
There is a deadline for how long it can take for the proposal to get executed. Again, as the proposal is necessarily something that has been agreed by the voters, it will be incentivized naturally to be executed.
See [tech-design/authority-token.md](./authority-tokens.md) for how effects take place after execution

View file

@ -0,0 +1,37 @@
# Vesting contract technical design
| Spec Status | Impl Status | Last Revision |
|-------------|-------------|---------------|
| WIP | WIP | 2022-01-18 |
--------------------
**Spec Ownership:** [@Emily Martins]
**Authors**: [@Emily Martins]
**Impl Owner:** [@Emily Martins]
[@Emily Martins]: https://github.com/emiflake
--------------------
## Vesting contract
In order to distribute governance tokens, one or more vesting contracts may be deployed.
First and foremost, there is a _schedule_ for the distribution of said tokens. Alongside this schedule, we keep track of how much we've distributed so far. This allows us to calculate how much is "due" at any particular time.
To gain intuition, you can think of it being implemented this way:
```haskell
distributed :: Schedule -> Time -> Value
distributed schedule time = scanl (+) mempty schedule !! time
due :: Schedule -> Value -> Time -> Value
due schedule alreadyDistributed time = distributed schedule time - alreadyDistributed
```
The vesting contract may require that only an address in a particular set can withdraw from it.
The vesting contract may have an optional escape hatch for trusted authority to recover from a potential DAO operation.