Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs #1391

Open
wants to merge 3 commits into
base: develop-combo-futarchy
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ decentralized court.
## Modules

- [authorized](./zrml/authorized) - Offers authorized resolution of disputes.
- [combinatorial-tokens](./zrml/combinatorial-tokens) - The module responsible
for generating Zeitgeist 2.0 outcome tokens.
- [court](./zrml/court) - An implementation of a court mechanism used to resolve
disputes in a decentralized fashion.
- [futarchy](./zrml/futarchy) - A novel on-chain governance mechanism using
prediction markets.
- [global-disputes](./zrml-global-disputes) - Global disputes sets one out of
multiple outcomes with the most locked ZTG tokens as the canonical outcome.
This is the default process if a dispute mechanism fails to resolve.
Expand Down
2 changes: 2 additions & 0 deletions primitives/src/traits/combinatorial_tokens_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ use crate::types::SplitPositionDispatchInfo;
use alloc::vec::Vec;
use sp_runtime::DispatchError;

/// Trait that can be used to expose the internal functionality of zrml-combinatorial-tokens to
/// other pallets.
pub trait CombinatorialTokensApi {
type AccountId;
type Balance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use alloc::vec::Vec;
use sp_runtime::DispatchResult;

/// Trait used for setting up benchmarks of zrml-combinatorial-tokens. Must not be used in
/// production.
pub trait CombinatorialTokensBenchmarkHelper {
type Balance;
type MarketId;
Expand Down
6 changes: 6 additions & 0 deletions primitives/src/traits/combinatorial_tokens_unsafe_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,19 @@ pub trait CombinatorialTokensUnsafeApi {
type Balance;
type MarketId;

/// Transfers `amount` units of collateral from the user to the pallet's reserve and mints
/// `amount` units of each asset in `assets`. Can break the reserve or result in loss of funds
/// if the value of the elements in `assets` don't add up to exactly 1.
fn split_position_unsafe(
who: Self::AccountId,
collateral: Asset<Self::MarketId>,
assets: Vec<Asset<Self::MarketId>>,
amount: Self::Balance,
) -> DispatchResult;

/// Transfers `amount` units of collateral from the pallet's reserve to the user and burns
/// `amount` units of each asset in `assets`. Can break the reserve or result in loss of funds
/// if the value of the elements in `assets` don't add up to exactly 1.
fn merge_position_unsafe(
who: Self::AccountId,
collateral: Asset<Self::MarketId>,
Expand Down
39 changes: 37 additions & 2 deletions zrml/combinatorial-tokens/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# Combo Module
# Combinatorial Tokens Module

The Combo module implements combinatorial tokens in substrate.
The combinatorial-tokens module implements modern Zeitgeist's method of
creating and destroying outcome tokens.

## Overview

In a categorical or scalar prediction market, one unit of a complete set (i.e. one unit of each outcome token of the market) always redeems for one unit of collateral.

In a Yes/No market, for instance, holding `x` units of Yes and `x` units of No means that, when the market resolves, you will always receive `x` units of collateral. In a scalar market, on the other hand, `x` units of Long and `x` units of Short will always redeem to a total of `x` units of collateral, as well.

This means that buying and selling collateral for complete sets should be allowed. For example, `x` units of collateral should fetch `x` units of complete set, and vice versa. Buying complete sets can be thought of as splitting collateral into outcome tokens, while selling complete sets can be thought of as merging outcome tokens back into collateral.

The combinatorial-tokens module generalizes this approach to not only allow splitting and merging into collateral, but also splitting and merging into outcome tokens of multiple different markets. This allows us to create outcome tokens that combine multiple events. They are called _combinatorial tokens_.

For example, splitting an `A` token from one categorical market using another categorical market with two outcomes `X` and `Y` yields `A & X` and `A & Y` tokens. They represent the event that `A` and `X` (resp. `Y`) occur. Splitting a Yes token from a binary market using a scalar market will give `Yes & Long` and `Yes & Short` tokens. They represent Long/Short tokens contingent on `Yes` occurring.

In addition to splitting and merging, combinatorial tokens can be redeemed if one of the markets involved in creating them has been resolved. For example, if the `XY` market above resolves to `X`, then every unit of `X & A` redeems for a unit of `A` and `Y & A` is worthless. If the scalar market above resolves so that `Long` is valued at `.4` and `Short` at `.6`, then every unit of `Yes & Long` redeems for `.4` units of `Yes` and every unit of `Yes & Short` redeems for `.6`.

And important distinction which we've so far neglected to make is the distinction between an abstract _collection_ like `X & A` or `Yes & Short` and a concrete _position_, which is a collection together with a collateral token against which it is valued. Collections are purely abstract and used in the implementation. Positions are actual tokens on the chain.

Collections and position are identified using their IDs. When using the standard combinatorial ID Manager, this ID is a 256 bit value. The position ID of a certain token can be calculated using the collection ID and the collateral.

### Terminology

- _Combinatorial token_: Any instance of `zeitgeist_primitives::Asset::CombinatorialToken`.
- _Complete set (of a prediction market)_: An abstract set containing every outcome of a particular prediction market. One unit of a complete set is one unit of each outcome token from the market in question. After the market resolves, a complete set always redeems for exactly one unit of collateral.
- _Merge_: The prcoess of exchanging multiple tokens for a single token of equal value.
- _Split_: The process of exchanging a token for more complicated tokens of equal value.

### Combinatorial ID Manager

Calculating

alt_bn128

combinatorial tokens, as [defined by
Gnosis](https://docs.gnosis.io/conditionaltokens/) in Substrate.
67 changes: 66 additions & 1 deletion zrml/combinatorial-tokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ mod pallet {
MarketId = MarketIdOf<Self>,
>;

/// Interface for calculating collection and position IDs.
type CombinatorialIdManager: CombinatorialIdManager<
Asset = AssetOf<Self>,
MarketId = MarketIdOf<Self>,
Expand All @@ -86,6 +87,7 @@ mod pallet {

type MultiCurrency: MultiCurrency<Self::AccountId, CurrencyId = AssetOf<Self>>;

/// Interface for acquiring the payout vector by market ID.
type Payout: PayoutApi<Balance = BalanceOf<Self>, MarketId = MarketIdOf<Self>>;

type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
Expand Down Expand Up @@ -195,6 +197,32 @@ mod pallet {

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Split `amount` units of the position specified by `parent_collection_id` over the market
/// with ID `market_id` according to the given `partition`.
///
/// The `partition` is specified as a vector whose elements are equal-length `Vec<bool>`. A
/// `true` entry at the `i`th index of a partition element means that the `i`th outcome
/// token of the market is contained in this element of the partition.
///
/// For each element `b` of the partition, the split mints a new outcome token which is made
/// up of the position to be split and the conjunction `(x|...|z)` where `x, ..., z` are the
/// items of `b`. The position to be split, in turn, is burned or transferred into the
/// pallet account, depending on whether or not it is a true combinatorial token or
/// collateral.
///
/// If the `parent_collection_id` is `None`, then the position split is the collateral of the
/// market given by `market_id`.
///
/// If the `parent_collection_id` is `Some(pid)`, then there are two cases: vertical and
/// horizontal split. If `partition` is complete (i.e. there is no index `i` so that `b[i]`
/// is `false` for all `b` in `partition`), the position split is the position obtained by
/// combining `pid` with the collateral of the market given by `market_id`. If `partition`
/// is not complete, the position split is the position made up of the
/// `parent_collection_id` and the conjunction `(x|...|z)` where `x, ..., z` are the items
/// covered by `partition`.
///
/// The `force_max_work` parameter can be used to trigger the maximum amount of allowed work
/// for the combinatorial ID manager. Should only be used for benchmarking purposes.
#[pallet::call_index(0)]
#[pallet::weight(
T::WeightInfo::split_position_vertical_sans_parent(partition.len().saturated_into())
Expand All @@ -206,7 +234,6 @@ mod pallet {
#[transactional]
pub fn split_position(
origin: OriginFor<T>,
// TODO Abstract this into a separate type.
parent_collection_id: Option<CombinatorialIdOf<T>>,
market_id: MarketIdOf<T>,
partition: Vec<Vec<bool>>,
Expand All @@ -227,6 +254,31 @@ mod pallet {
DispatchResultWithPostInfo::Ok(post_dispatch_info)
}

/// Merge `amount` units of the tokens obtained by splitting `parent_collection_id` using
/// `partition` into the position specified by `parent_collection_id` (vertical split) or
/// the position obtained by splitting `parent_collection_id` according to `partiton` over
/// the market with ID `market_id` (horizontal; see below for details).
///
/// The `partition` is specified as a vector whose elements are equal-length `Vec<bool>`. A
/// `true` entry at the `i`th index of a partition element means that the `i`th outcome
/// token of the market is contained in this element of the partition.
///
/// For each element `b` of the partition, the split burns the outcome tokens which are made
/// up of the position to be split and the conjunction `(x|...|z)` where `x, ..., z` are the
/// items of `b`. The position given by `parent_collection_id` is
///
/// If the `parent_collection_id` is `None`, then the position split is the collateral of the
/// market given by `market_id`.
///
/// If the `parent_collection_id` is `Some(pid)`, then there are two cases: vertical and
/// horizontal merge. If `partition` is complete (i.e. there is no index `i` so that `b[i]`
/// is `false` for all `b` in `partition`), the the result of the merge is the position
/// defined by `parent_collection_id`. If `partition` is not complete, the result of the
/// merge is the position made up of the `parent_collection_id` and the conjunction
/// `(x|...|z)` where `x, ..., z` are the items covered by `partition`.
///
/// The `force_max_work` parameter can be used to trigger the maximum amount of allowed work
/// for the combinatorial ID manager. Should only be used for benchmarking purposes.
#[pallet::call_index(1)]
#[pallet::weight(
T::WeightInfo::merge_position_vertical_sans_parent(partition.len().saturated_into())
Expand Down Expand Up @@ -255,6 +307,19 @@ mod pallet {
)
}

/// (Partially) redeems a position if part of it belongs to a resolved market given by
/// `market_id`.
///
/// The position to be redeemed is the position obtained by combining the position given by
/// `parent_collection_id` and `collateral` with the conjunction `(x|...|z)` where `x, ...
/// z` are the outcome tokens of the market `market_id` given by `partition`.
///
/// The position to be redeemed is completely removed from the origin's wallet. According to
/// how much the conjunction `(x|...|z)` is valued, the user is paid in the position defined
/// by `parent_collection_id` and `collateral`.
///
/// The `force_max_work` parameter can be used to trigger the maximum amount of allowed work
/// for the combinatorial ID manager. Should only be used for benchmarking purposes.
#[pallet::call_index(2)]
#[pallet::weight(
T::WeightInfo::redeem_position_with_parent(index_set.len().saturated_into())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,27 @@

use alloc::vec::Vec;

/// Handles calculations of combinatorial IDs.
pub trait CombinatorialIdManager {
type Asset;
type MarketId;
type CombinatorialId;

// TODO Replace `Vec<bool>` with a more effective bit mask type.
/// Calculate the collection ID obtained when splitting `parent_collection_id` over the market
/// given by `market_id` and the `index_set`.
///
/// If `force_max_work` parameter is set, the calculation will use up the maximum amount of work
/// necessary, independent of the other parameters. Should only be used for benchmarking
/// purposes.
fn get_collection_id(
parent_collection_id: Option<Self::CombinatorialId>,
market_id: Self::MarketId,
index_set: Vec<bool>,
force_max_work: bool,
) -> Option<Self::CombinatorialId>;

/// Calculate the position ID belonging to the `collection_id` combined with `collateral` as
/// collateral.
fn get_position_id(
collateral: Self::Asset,
collection_id: Self::CombinatorialId,
Expand Down
45 changes: 45 additions & 0 deletions zrml/futarchy/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,46 @@
# Futarchy Module

The futarchy module provides a straightforward, "no bells and whistles"
implementation of the [futarchy governance
system](https://docs.zeitgeist.pm/docs/learn/futarchy).

## Overview

The futarchy module is essentially an oracle based governance system: When a
proposal is submitted, an oracle is specified which evaluates whether the
proposal should be executed. The type of the oracle is configured using the
associated type `Oracle`, which must implement `FutarchyOracle`.

The typical oracle implementation for futarchy is the `DecisionMarketOracle`
implementation exposed by the neo-swaps module, which allows making decisions
based on prices in prediction markets. A `DecisionMarketOracle` is defined by
providing a pool ID and two outcomes, the _positive_ and _negative_ outcome.
The oracle evaluates positively (meaning that it will allow the proposal to
pass) if and only if the positive outcome is move valuable than the negative
outcome.

The standard governance flow is the following:

- A hard-coded origin (usually root) submits a proposal to be approved or
rejected via futarchy. If the origin is root, this is done by running a
governance proposal through
[pallet-democracy](https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/democracy)
and calling into this pallet's sole extrinsic `submit_proposal`. Assuming
that the thesis of futarchy is correct and the market used to evaluate the
proposal is well-configured and sufficiently liquid, submitting a proposal to
futarchy rather than pallet-democracy gives a stronger guarantee on the
efficacy of the proposal.
- Wait until the `duration` specified in `submit_proposal` has passed. The
oracle will be automatically evaluated and will either schedule
`proposal.call` at `proposal.when` where `proposal` is the proposal specified
in `submit_proposal`.

### Terminology

- _Call_: Refers to an on-chain extrinsic call.
- _Oracle_: A means of making a decision about a proposal. At any block, an
oracle evaluates to `true` (proposal is accepted) or `false` (proposal is
rejected).
- _Proposal_: Consists of a call, an oracle and a time of execution. If and
only if the proposal is accepted, the call is scheduled for the specified
time of execution.
7 changes: 6 additions & 1 deletion zrml/futarchy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,10 @@ mod pallet {
#[cfg(feature = "runtime-benchmarks")]
type BenchmarkHelper: FutarchyBenchmarkHelper<Self::Oracle>;

/// The minimum allowed duration between the creation of a proposal and its evaluation.
type MinDuration: Get<BlockNumberFor<Self>>;

// The type used to define the oracle for each proposal.
/// The type used to define the oracle a proposal.
type Oracle: FutarchyOracle
+ Clone
+ Debug
Expand Down Expand Up @@ -152,6 +153,10 @@ mod pallet {

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Submits a `proposal` for evaluation in `duration` blocks.
///
/// If, after `duration` blocks, the oracle `proposal.oracle` is evaluated positively, the
/// proposal is scheduled for execution at `proposal.when`.
#[pallet::call_index(0)]
#[transactional]
#[pallet::weight(T::WeightInfo::submit_proposal())]
Expand Down
6 changes: 5 additions & 1 deletion zrml/futarchy/src/types/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ use frame_system::pallet_prelude::BlockNumberFor;
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;

// TODO Make config a generic, keeps things simple.
#[derive(
CloneNoBound, Decode, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo,
)]
Expand All @@ -30,7 +29,12 @@ pub struct Proposal<T>
where
T: Config,
{
/// The time at which the proposal will be enacted.
pub when: BlockNumberFor<T>,

/// The proposed call.
pub call: BoundedCallOf<T>,

/// The oracle that evaluates if the proposal should be enacted.
pub oracle: OracleOf<T>,
}
Loading