Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1e2d990
feat(platform)!: multiple choice decision proposals, voted on as a co…
QuantumExplorer Nov 10, 2026
141db1c
fix(dpp): allow the shared prefix of the stored contested resolution'…
QuantumExplorer Nov 10, 2026
fb2ed1c
fix(dpp): read a contested index's resolution through a versioned method
QuantumExplorer Nov 10, 2026
cd6f790
feat(platform)!: document references whose target the document names …
QuantumExplorer Nov 10, 2026
52b91da
test(dpp): charge a contested index resolved among documents its regi…
QuantumExplorer Nov 10, 2026
82fc9b2
feat(platform)!: either-kind targets and slot conditions for document…
QuantumExplorer Nov 10, 2026
f430c9a
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 10, 2026
d2e04ba
feat(platform)!: multiple choice proposals take their description fro…
QuantumExplorer Nov 10, 2026
c3485d2
fix(platform): refuse index-only dynamic targets and moderator-writte…
QuantumExplorer Nov 10, 2026
281b1a6
Merge branch 'claude/dynamic-refers-to' into claude/dynamic-refers-to…
QuantumExplorer Nov 10, 2026
48cb3b4
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 10, 2026
d734f2b
fix(dpp): restore the line continuation in a dynamic reference refusal
QuantumExplorer Nov 11, 2026
60eef82
Merge remote-tracking branch 'origin/claude/dynamic-refers-to' into c…
QuantumExplorer Nov 11, 2026
a7f9bd3
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
a6d4c44
fix(dpp): hold a dynamic reference's require to terms named once
QuantumExplorer Nov 11, 2026
f43ae0c
Merge remote-tracking branch 'origin/claude/dynamic-refers-to' into c…
QuantumExplorer Nov 11, 2026
3746732
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
decfcc4
feat(platform)!: name a document-named target with $self. on contract…
QuantumExplorer Nov 11, 2026
e60bed6
Merge remote-tracking branch 'origin/claude/dynamic-refers-to' into c…
QuantumExplorer Nov 11, 2026
a2b23ac
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
0216863
fix(dpp): hold a string slot to the tighter of a property's two bounds
QuantumExplorer Nov 11, 2026
2d7a341
Merge remote-tracking branch 'origin/claude/dynamic-refers-to' into c…
QuantumExplorer Nov 11, 2026
1c39285
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
31697df
fix(platform): read stored references as registered and keep their er…
QuantumExplorer Nov 11, 2026
d5a580d
Merge remote-tracking branch 'origin/claude/dynamic-refers-to' into c…
QuantumExplorer Nov 11, 2026
ee561af
fix(platform): bill and bound the judging of a slot's when
QuantumExplorer Nov 11, 2026
f631ac4
Merge remote-tracking branch 'origin/v5.1-dev' into claude/dynamic-re…
QuantumExplorer Nov 11, 2026
c50725a
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
065f9a2
fix(platform): require a description host whose owner the elected tea…
QuantumExplorer Nov 11, 2026
bc2b590
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 11, 2026
61b5db3
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 11, 2026
78ebef7
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 11, 2026
b8b5a39
Merge remote-tracking branch 'origin/v5.1-dev' into claude/dynamic-re…
QuantumExplorer Nov 11, 2026
78165e1
Merge branch 'claude/dynamic-refers-to-when' into claude/decision-pro…
QuantumExplorer Nov 11, 2026
c47aa85
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 11, 2026
e2bea23
feat(platform)!: require a description the elected team may always de…
QuantumExplorer Nov 12, 2026
2a56923
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 12, 2026
231d153
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
6d36466
test(drive-abci): pin the contested consume fee with the contests of …
QuantumExplorer Nov 12, 2026
0f127e1
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
3e981fa
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 12, 2026
89a7e5f
docs(contract): say when a choice leaves state and how its fund grows
QuantumExplorer Nov 12, 2026
e0541b6
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
3008116
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 12, 2026
486a8d6
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
a671a6c
Merge remote-tracking branch 'origin/v5.1-dev' into claude/decision-p…
QuantumExplorer Nov 12, 2026
b0ebaec
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
8e77ae7
docs(contract): state the whole tie rule of a multiple choice vote
QuantumExplorer Nov 12, 2026
2bbbb4f
Merge branch 'claude/decision-proposal-contract-1db678' into claude/d…
QuantumExplorer Nov 12, 2026
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
2 changes: 1 addition & 1 deletion book/src/contract-keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ A typed array's element (`items`) takes `type` (the `identifier` and `bytes` sho
| `documentType` | document type name | The referenced document type. | 14 | [documentType](contract-keywords/refers-to.md#documenttype) |
| `contractId` | identifier | The contract holding `documentType`, when it is not this one. | 14 | [contractId](contract-keywords/refers-to.md#contractid) |
| `contractId`, `documentType` as `"$self.<property>"` | `$self.` and a top-level property name | On a `permanentDocument` or `moderatedDocument`, or a `type` [listing both](contract-keywords/refers-to-dynamic.md#either-kind): both read from the referring document, an identifier property holding the id of the contract and a string property holding the name of the document type. Both or neither. | 15 | [A Target the Document Names](contract-keywords/refers-to-dynamic.md) |
| `documentTypeRequirements` | `{ "properties", "require"? }` | Beside a target read from the document: 1 to 4 named slots, each a property the named type must have (`name`, `type`, `maxBytes`, `distinctFrom`, and `when`, a [condition](contract-keywords/refers-to-dynamic.md#a-condition-on-the-property-when) on what the type says of the property), and which of them must be filled. | 15 | [documentTypeRequirements](contract-keywords/refers-to-dynamic.md#documenttyperequirements) |
| `documentTypeRequirements` | `{ "properties", "require"? }` | Beside a target read from the document: 1 to 4 named slots, each a property the named type must have (`name`, `type`, `maxBytes`, `distinctFrom`, and `when`, a [condition](contract-keywords/refers-to-dynamic.md#a-condition-on-the-property-when) on what the type says of the property), which of them must be filled, and a [`when` on the type as a whole](contract-keywords/refers-to-dynamic.md#a-condition-on-the-type-when). | 15 | [documentTypeRequirements](contract-keywords/refers-to-dynamic.md#documenttyperequirements) |
| `findBy` | 1 to 10 entries: referenced property → `"."`, `"$ownerId"`, a path or a function | Finds the document through the unique index of `documentType` over exactly these properties, in any order; `"."` is the value itself. Without it the value is the document's `$id`. | 14 | [findBy](contract-keywords/refers-to-lookup.md) · [internals](data-model/documents.md#found-by-a-unique-index-findby) |
| `findBy.<property>` | `{ "function": "sys.hash.sha256d", "params" }` | A function entry: the referenced property holds the hash of `params` (paths, `{ "const": text }`, `"."` for a value without a path), which fills that part of the key, finding a commitment made earlier. At most one. | 14 | [Commit and reveal](contract-keywords/refers-to-lookup.md#commit-and-reveal) · [internals](data-model/documents.md#commit-and-reveal-a-findby-function) |
| `where` | 1 to 10 entries `{ "<referenced property>": "<referring value>" }` | Checked on the document found: each referenced property (or `$ownerId`, `$creatorId`, `$id`) must equal the referring property. A value of `"$ownerId"`, the writer, makes a write gate. Never finds the document. | 14 | [where](contract-keywords/refers-to.md#where) |
Expand Down
10 changes: 5 additions & 5 deletions book/src/contract-keywords/contested.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A unique index normally works first come, first served: the first document to ta
|---|---|
| **Where** | a unique index |
| **Value** | object: `resolution` (required), `fieldMatches`, `description` |
| **Since** | protocol version 1; `resolution: 1` from protocol version 14 |
| **Since** | protocol version 1; `resolution: 1` from protocol version 14; `resolution: 2` from protocol version 15 |
| **On update** | Fixed, like every index (`DataContractInvalidIndexDefinitionUpdateError`, 10217) |
| **Errors** | `DocumentContestNotPaidForError` (40114), `DocumentContestCurrentlyLockedError` (40110), `DocumentContestNotJoinableError` (40111), `DocumentContestIdentityAlreadyContestantError` (40112), `DocumentContestIndexMismatchError` (40118), `DocumentContestNotRequiredError` (40119), `DocumentContestMaximumContendersReachedError` (40141); `DuplicateUniqueIndexError` (40105) for values outside the contested range |

Expand Down Expand Up @@ -38,7 +38,7 @@ A name is unique under its parent domain. A label of 3 to 19 characters made of

| Key | Value | What it does |
|---|---|---|
| `resolution` | `0` or `1`, required | How the contest is decided. `0`: masternodes vote for a contender, abstain, or **lock** the value so nobody gets it. `1` (from protocol version 14): masternodes vote for a contender or abstain; there is no lock, so the contest always ends with a winner. |
| `resolution` | `0`, `1` or `2`, required | How the contest is decided. `0`: masternodes vote for a contender, abstain, or **lock** the value so nobody gets it. `1` (from protocol version 14): masternodes vote for a contender or abstain; there is no lock, so the contest always ends with a winner. `2` (from protocol version 15): as `0`, but each document is a contender of its own, named by its document id, so an identity may enter several. |
| `fieldMatches` | array of at least one `{ "field", "regexPattern" }` | Which values are contested. `field` is a property path of the document; `regexPattern` a regular expression its value must match. Each is 1 to 256 characters. |
| `description` | string, 1 to 256 characters | A note for readers. Consensus does not read it. |

Expand All @@ -54,11 +54,11 @@ A name is unique under its parent domain. A label of 3 to 19 characters made of
- with `DocumentContestIndexMismatchError` (40118, from protocol version 14) when the pair names another index than the contested one its values match.
- with `DocumentContestNotRequiredError` (40119, from protocol version 14) when the document is not contested but carries a prefunded balance.
- with `DocumentContestNotJoinableError` (40111) when the contest's join window (one week on mainnet) has passed.
- with `DocumentContestIdentityAlreadyContestantError` (40112) when its owner is already a contender.
- with `DocumentContestIdentityAlreadyContestantError` (40112) when its owner is already a contender, under `resolution: 0` and `1`. Under `resolution: 2` an identity may enter as many documents as it wants, each paying the fund.
- with `DocumentContestMaximumContendersReachedError` (40141, from protocol version 14) when the contest already holds 1,000 contenders.
- with `DocumentContestCurrentlyLockedError` (40110) when an earlier contest for these values ended locked.

**The vote.** Masternodes and evonodes vote with `MasternodeVote` transitions for the length of the poll (two weeks on mainnet). Under `resolution: 0` the contender with the most votes wins unless the lock tally beats it, and the contest always runs its full length, even with one contender. Under `resolution: 1` a contender always wins, and a contest that still has a single contender when its join window closes is awarded to it at once. From protocol version 14 a tie goes to the earliest contender.
**The vote.** Masternodes and evonodes vote with `MasternodeVote` transitions for the length of the poll (two weeks on mainnet). Under `resolution: 0` the contender with the most votes wins unless the lock tally beats it, and the contest always runs its full length, even with one contender. Under `resolution: 1` a contender always wins, and a contest that still has a single contender when its join window closes is awarded to it at once. Under `resolution: 2` the vote runs as under `0`, lock included, but among documents: a vote towards a contender names a document id where the other resolutions name the owner's identity id, and the result names the winning document. From protocol version 14 a tie goes to the earliest contender.

**After the contest.** The winning document is stored and held by the index like any unique-index document; the other contenders' documents are removed. The contest's result stays readable.

Expand All @@ -69,7 +69,7 @@ The fund, the windows, the tallies and the special case of moderation elections
- The index is `unique: true`. A contested index that is not unique is refused (`InvalidContractStructure`, 10231).
- The document type's documents cannot be replaced: `documentsMutable: false` (`ContestedUniqueIndexOnMutableDocumentTypeError`, 10248). They may still be transferred and sold, as DPNS domains are.
- A document type has at most one contested index, and no other unique index beside it (`ContestedUniqueIndexWithUniqueIndexError`, 10249).
- `resolution` is present and is `0` or `1`; `1` is refused before protocol version 14.
- `resolution` is present and is `0`, `1` or `2`; `1` is refused before protocol version 14 and `2` before protocol version 15.
- `fieldMatches`, when present, holds at least one entry, and each `regexPattern` is a valid regular expression (`RegexError`, 10247).
- From protocol version 15, the contest is decided by the index values, so that two documents with the same values are both contested or both ordinary: every `fieldMatches` field is a property of the index, and a match on a string property that may be empty (no `minLength` of at least 1) treats `""` and `"\0"` alike, since the two share one index key (`InvalidContractStructure`, 10231). Otherwise an ordinary document could take the values of a contested one, and the contest would have nothing left to award. A contract registered earlier keeps its contest, but can no longer be updated, since an update is checked like a registration; while such a contest is live, a create outside it with the same index values is refused (`DuplicateUniqueIndexError`).
- From protocol version 15, every property of the index is a top-level property of the document: no property path such as `records.identity` and no system property such as `$ownerId` (`InvalidContractStructure`, 10231). A contested create finds the contest by reading each index value from the document's top-level properties, where such a property is never found, so no contested create could open or join the contest. A contract registered earlier keeps such an index, but can no longer be updated.
Expand Down
30 changes: 28 additions & 2 deletions book/src/contract-keywords/refers-to-dynamic.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ A note is described by a document its author points at: `descriptionContractId`
|---|---|---|
| `contractId` | `"$self."` followed by the name of a top-level identifier property of the document type, 1 to 64 letters, digits or underscores | The property holding the id of the contract the referenced document type lives in, where a reference naming its target in the schema writes the id itself. |
| `documentType` | `"$self."` followed by the name of a top-level string property | The property holding the name of the referenced document type, where a reference naming its target in the schema writes the name itself. |
| `documentTypeRequirements` | object: `properties` (required), `require` | What the named document type must look like. Absent, the type is held to its kind alone. |
| `documentTypeRequirements` | object: `properties` (required), `require`, `when` | What the named document type must look like. Absent, the type is held to its kind alone. |

The two come together: a declaration names its target in the schema or through the document, never half of each, so a `$self.` `contractId` beside a literal `documentType` is refused, as is one beside none. The declaring contract is named like any other: a document pointing into its own contract writes that contract's id. Nothing a schema could write literally starts with `$self.`: a contract id is base58 and a document type name holds no `$`.

Expand Down Expand Up @@ -140,18 +140,42 @@ When the type is a moderated one, the string filling `body` is one a moderator's
| `"$property"` | The name of the property tried for the slot. |
| `"$moderatorAbilities.deleteKeepsFields"` | The referenced type's [`deleteKeepsFields`](moderator-abilities.md): the property paths a removal record keeps. Empty when the type lists none. |
| `"$moderatorAbilities.changeFields"` | The referenced type's [`changeFields`](moderator-abilities.md): the properties only moderators write. Empty when the type lists none. |
| `"$electedAbilities"` | The abilities the referenced type's contract gives its [elected moderation team](../data-model/contract-moderation.md) on the type, by name: `deleteDocuments`, `ban`, `suspend`, `warn`, `changeDocumentFields`. Empty when the contract elects no team, or leaves the type out of what the team moderates. |
| `"$moderatorAbilities.deleteWithin"`, `"$moderatorAbilities.deleteSettled"` | Settings of the referenced type a `present` or an `absent` tests: whether moderators delete a document only within a window after its last change, and whether the type says who of a seated team may delete one past it. |
| `{ "const": "<string>" }` | The string, 1 to 256 characters. |

| Condition | Holds when |
|---|---|
| `{ "equal": [a, b] }`, `{ "notEqual": [a, b] }` | `$kind` or `$property` is, or is not, the constant on the other side. Compared with `$kind`, the constant is `permanentDocument` or `moderatedDocument`. |
| `{ "in": [a, [...]] }`, `{ "notIn": [a, [...]] }` | `$kind` or `$property` is, or is not, one of two or more distinct strings. |
| `{ "contains": [list, a] }` | One of the two lists holds `$property` or a constant, as a whole entry. |
| `{ "contains": [list, a] }` | One of the two property lists holds `$property` or a constant, as a whole entry; or `$electedAbilities` holds the ability a constant names. |
| `{ "present": setting }`, `{ "absent": setting }` | The type declares, or does not declare, `$moderatorAbilities.deleteWithin` or `$moderatorAbilities.deleteSettled`. |
| `{ "not": c }`, `{ "anyOf": [...] }`, `{ "allOf": [...] }` | As in `propertyConstraints`: `anyOf` and `allOf` take two or more conditions, none listed twice and none the same combinator; `not` holds no `not` and no `notIn`. |
| `{ "ifThen": [c, then] }`, `{ "ifThenElse": [c, then, else] }` | `then` holds whenever `c` does; with an `else`, it holds when `c` does not. |

One `when` is made of at most 16 conditions (`max_document_type_requirement_condition_nodes`), combinators counted and each string an `in` or a `notIn` lists counted too, and no string in it is longer than 256 characters (`max_document_type_requirement_condition_text_length`), so what it adds to a stored contract is bounded. It reads the type, never a document: no property path, no arithmetic, and no value of the referenced document. `distinctFrom`, `require` and the search for an assignment work as before, over the properties each slot's `when` leaves it.

## A condition on the type (`when`)

`documentTypeRequirements` takes a `when` of its own, beside `properties`: the same condition, judged once for the named type as a whole, whatever fills the slots. No property is in hand, so it reads no `$property`. A type it does not hold for does not meet the requirements (40153).

```json
"documentTypeRequirements": {
"when": { "allOf": [
{ "contains": ["$electedAbilities", { "const": "deleteDocuments" }] },
{ "anyOf": [
{ "absent": "$moderatorAbilities.deleteWithin" },
{ "present": "$moderatorAbilities.deleteSettled" }
] }
] },
"properties": { "body": { "name": "$any", "type": "string" } }
}
```

The contract of the named type gives its elected team the deletion of the type's documents, and a document stays deletable: the type sets no window, or says who of the team deletes one past it. Without the first, a type can be a moderated one that nobody may moderate once a team is seated; without the second, a document outlasts its window for good. A reference that relies on a document being removable asks for both, and usually for [an elected contract](#who-vouches-for-the-target) too.

A slot's `when` reads the same facts beside `$property`. Each `when` is held to the limits above on its own.

## How it works

Nothing about the target is known when the contract is registered, so registration checks the declaration alone. Everything about the target is checked when a document is written, in this order, the first failure refusing the write:
Expand Down Expand Up @@ -193,6 +217,8 @@ The requirements say what the target's type looks like. They do not say who runs

or compare it with the contracts the application trusts in a [`propertyConstraints`](property-constraints.md) rule (`{ "in": ["descriptionContractId", ["<base58>", "<base58>"]] }`).

The decision proposals contract does the first for the description of a multiple choice proposal: a `moderatedDocument` whose text a removal takes away, in a contract whose moderators the masternodes elect (see `docs/protocol/decision-proposals.md`). `"moderation": "elected"` says the contract declares such a team, not that one is seated: until then the contract's interim moderators, if it names any, are the ones who can remove the document.

## Errors

| Error | Code | When |
Expand Down
24 changes: 24 additions & 0 deletions book/src/data-model/contested-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ contest ends then. `getVotePollsByEndDate` shows whichever end applies.

The moderation charters contract uses this resolution to elect moderation teams.

## Resolution 2: masternode vote among documents

`ContestedIndexResolution::MasternodeVoteOnDocuments`, admitted where
`parse_contested_documents_resolution` is set (protocol version 15). The choices are a contender,
abstain, or lock, and the contest runs and ends exactly as under resolution 0. What differs is
what a contender is. Under resolutions 0 and 1 a contender is an identity: the contest keeps its
document under the identity's id, so an identity enters one document, and a vote, a tally and the
result name the identity. Under resolution 2 a contender is a document: the contest keeps it
under its document id, so an identity may enter several, each paying the fund and counting
towards `max_contenders_per_contest`, and a second document from the same identity is not
refused (no `DocumentContestIdentityAlreadyContestantError`).

Everything that names a contender then carries a document id where it carries an identity id
under the other resolutions: the identifier of a `TowardsIdentity` vote choice, a contender's
identifier in `getContestedResourceVoteState`, the contender asked for in
`getContestedResourceVotersForIdentity`, and the winner in the stored result. A vote towards an
identifier that names no contender is refused (`VoteChoiceNotAllowedForVotePollError`, 40307),
so a vote towards the id of an identity that entered documents is refused: the voter names the
document. The winning document is stored under the owner the document itself names.

The decision proposals contract uses this resolution for the `choice` documents entered for a
`multipleChoiceProposal`: the contest is keyed by the proposal's id, each choice is a contender,
and a lock means none of the choices (see `docs/protocol/decision-proposals.md`).

## Moderation elections

An `electedCharter` contest of the moderation charters contract (protocol version 14), keyed by
Expand Down
Loading
Loading