Skip to content

Commit

Permalink
Add docs (#1391)
Browse files Browse the repository at this point in the history
* Add some docs and remove TODOs

* More documentation

* .

* update docs to use fuel parameter

* Update zrml/combinatorial-tokens/README.md

* Update zrml/combinatorial-tokens/README.md

* fix comment

* add decision market oracle scoreboard to futarchy module doc

* update decision market oracle doc to include scoreboard

* add fuel parameter doc to complexity of deploy combinatorial pool

* correct the futarchy module documentation to use root origin for proposal

---------

Co-authored-by: Chralt98 <chralt98@gmail.com>
Co-authored-by: Chralt <chralt.developer@gmail.com>
  • Loading branch information
3 people authored Feb 10, 2025
1 parent d70e33a commit 57e9348
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 4 deletions.
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 @@ -22,6 +22,8 @@ use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
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`.

An 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 positions 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 process 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.
66 changes: 66 additions & 0 deletions zrml/combinatorial-tokens/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ mod pallet {
MarketId = MarketIdOf<Self>,
>;

/// Interface for calculating collection and position IDs.
type CombinatorialIdManager: CombinatorialIdManager<
Asset = AssetOf<Self>,
MarketId = MarketIdOf<Self>,
Expand All @@ -102,6 +103,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 @@ -213,6 +215,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 `fuel` parameter specifies how much work the cryptographic id manager will do
/// and can be used for benchmarking purposes.
#[pallet::call_index(0)]
#[pallet::weight(
T::WeightInfo::split_position_vertical_sans_parent(
Expand Down Expand Up @@ -251,6 +279,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 `fuel` parameter specifies how much work the cryptographic id manager will do
/// and can be used for benchmarking purposes.
#[pallet::call_index(1)]
#[pallet::weight(
T::WeightInfo::merge_position_vertical_sans_parent(
Expand Down Expand Up @@ -279,6 +332,19 @@ mod pallet {
Self::do_merge_position(who, parent_collection_id, market_id, partition, amount, fuel)
}

/// (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 `fuel` parameter specifies how much work the cryptographic id manager will do
/// and can be used for benchmarking purposes.
#[pallet::call_index(2)]
#[pallet::weight(
T::WeightInfo::redeem_position_with_parent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use crate::types::CollectionIdError;
use alloc::vec::Vec;

/// Handles calculations of combinatorial IDs.
pub trait CombinatorialIdManager {
type Asset;
type MarketId;
Expand All @@ -43,6 +44,8 @@ pub trait CombinatorialIdManager {
fuel: Self::Fuel,
) -> Result<Self::CombinatorialId, CollectionIdError>;

/// 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 more valuable than the negative outcome over
a period of time for a certain absolute and relative threshold determined by a
`DecisionMarketOracleScoreboard`.

The standard governance flow is the following:

- The root origin submits a proposal to be approved or rejected via futarchy 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 @@ -80,9 +80,10 @@ mod pallet {
/// The maximum number of proposals allowed to be in flight simultaneously.
type MaxProposals: Get<u32>;

/// 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 for a proposal.
type Oracle: FutarchyOracle<BlockNumber = BlockNumberFor<Self>>
+ Clone
+ Debug
Expand Down Expand Up @@ -154,6 +155,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
5 changes: 5 additions & 0 deletions zrml/futarchy/src/types/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,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

0 comments on commit 57e9348

Please sign in to comment.