Skip to content
Merged
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
29 changes: 18 additions & 11 deletions book/src/data-model/contract-moderation.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion book/src/error-handling/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ The fee category currently has a single code. The 30000 range is reserved for fu
| 40900-40904 | Shielded | `InvalidAnchorError` (40900), `NullifierAlreadySpentError` (40901), `InsufficientShieldedFeeError` (40904) |
| 41000-41003 | Contract Groups | `ContractGroupAlreadyExistsError` (41000), `ContractGroupNotFoundError` (41001), `IdentityNotContractGroupOwnerOrAdminError` (41002), `ContractGroupAdminNotFoundError` (41003) |
| 41100-41122 | Contract Moderation | `ContractModerationNotEnabledError` (41100), `IdentityNotContractModeratorError` (41101), `ContractUserBannedError` (41107), `ContractUserSuspendedError` (41108), `ContractModerationTargetNotFoundError` (41109), `ContractModeratorIdentityNotFoundError` (41110), `ContractFeesAlreadyClaimedThisEpochError` (41111), `ContractFeesNothingToClaimError` (41112), `ContractFeeClaimNotAllowedError` (41113), `ContractModerationCounterpartyBarredError` (41114), `DocumentTypeNotDeletableByModeratorsError` (41115), `DocumentModerationWindowElapsedError` (41116), `ContractUserNotWarnedError` (41117), `ContractUserWarningLimitReachedError` (41118), `ContractDocumentRemovalNotFoundError` (41119), `DocumentRestoreWindowElapsedError` (41120), `DocumentRestoreHashMismatchError` (41121), `ContractDocumentAlreadyRestoredError` (41122) |
| 41200-41299 | Contract Moderation Teams | `ContractModeratedDocumentTypeNotYetUsableError` (41200), `ContractModerationAbilityNotGrantedError` (41201), `ModerationCharterAddedModeratorLimitReachedError` (41202) |
| 41200-41299 | Contract Moderation Teams | `ContractModeratedDocumentTypeNotYetUsableError` (41200), `ContractModerationAbilityNotGrantedError` (41201), `ModerationCharterAddedModeratorLimitReachedError` (41202), `ModerationReasonNotListedError` (41203) |

Notice how the `DataTriggerError` sub-enum has its own `ErrorWithCode` implementation that the `StateError` delegates to:

Expand Down
35 changes: 28 additions & 7 deletions docs/protocol/moderation-charters.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ A ground for a moderation action.

| Property | Type | Meaning |
| --- | --- | --- |
| `code` | string, three uppercase letters, required | Unique among the owner's reasons (`byOwnerCode`, unique on `$ownerId` and `code`); what an action shows |
| `code` | string, three uppercase letters, required | Unique among the owner's reasons (`byOwnerCode`, unique on `$ownerId` and `code`); what an action shows. An action names the reason by its document id, in its reason's `reasonDocumentId` |
| `label` | string, 1 to 64 characters, required | The reason's name, such as Spam |
| `description` | string, 1 to 1024 characters | What the reason covers and how the team applies it |

Expand All @@ -85,7 +85,7 @@ bound to it, the key join requests are encrypted to.
| `description` | string, 1 to 4096 characters and at most 4096 bytes (`maxBytes`), required | What the team would moderate and how, for joiners and voters. Informational |
| `reasons` | typed array of at most 64 unique identifiers, required, each `refersTo` a `reason` | The moderation reasons the team's actions may name; empty is allowed, a team that can take no action; a missing reason refuses the create, naming the element (`reasons[2]`) |
| `moderatorsShare` | integer 0 to 100 | The percentage of each moderated document type's declared moderators fee the team takes, rounded down to the credit. Absent is the full amount; a lower number is a discount an action may agree to once the team is seated; 0 is a team that will not moderate and takes no rewards |
| `rewardSplit` | object, required | `leader`, `equal` and `actions`, three percentages summing to 100: the leader's share, the share split equally among the other members, and the share split by each member's action count. The sum is the type's `propertyConstraints` rule `rewardSplitIsWhole`, checked on every create (`DocumentPropertyConstraintViolatedError`, 10422) |
| `rewardSplit` | object, required | `leader`, `equal` and `actions`, three percentages summing to 100: the leader's share, the share split equally among the other members (the leader's when it has none), and the share split between the whole team, the leader included, by each one's action count since the last settle (equally when nobody acted). The sum is the type's `propertyConstraints` rule `rewardSplitIsWhole`, checked on every create (`DocumentPropertyConstraintViolatedError`, 10422) |

Indexes: `byTargetContract` (`targetContractId`, `$createdAt`) lists the
proposals for a contract in filing order; `byOwner` (`$ownerId`) lists a
Expand Down Expand Up @@ -238,9 +238,28 @@ replaced). The moderation paths of the target read it:
at the cost of the charter lookup and the proposal fetch
(`DocumentActionFeeModeratorsShareMismatchError`, 40139, for any other
amount, and for a discount with no seated charter).
- **Reasons.** Every ban, suspension, warning and document deletion of the
team names, in its reason's `reasonDocumentId`, a `reason` document the
seated proposal lists; any other is refused, paid, in a block and in the
mempool (`ModerationReasonNotListedError`, 41203), a reason naming none
included, so a proposal listing no reason can take no such action. The
proposal is read, billed, only when a reason document is named. Lifting and
restoring carry no reason and are not checked, and the interim is not bound.
- **The pot.** The interim team's claim of the moderators pot is refused once
a charter is seated (41113); the pot waits for the seated team, whose claim
comes in a later pull request.
a charter is seated (41113); the pot carries over to the seated team. The
leader or an active member claims it for the team, at most once per epoch,
and it is paid out by the proposal's `rewardSplit`: the leader share to the
leader, the equal share in equal parts to the other active members (to the
leader when there are none), and the action share between the whole team
by the bans, suspensions, warnings and document deletions each one signed
since the last settle, equally when nobody acted. Every share and every
part rounds down to the credit; what is left stays in the pot. The counts
live under the target contract (key `48` of its other tree) and every
settle deletes them.
- **Settles before team changes.** Creating or deleting an `addedModerator`
or a `removedModerator` first pays the pot out to the team as it was, the
same way, and resets the counts, whatever the epoch's claim: the settle
writes no last claim, and the team may still claim in the same epoch.
- **Resignations.** A `resignationRequest` changes nothing by itself: the
leader acts on it by deleting the member's `addedModerator`, or with a
`removedModerator` for an elected member.
Expand All @@ -253,9 +272,11 @@ written, the description's 4096-byte cap and the reward split's sum included:
`DocumentPropertyMaxBytesExceededError` (10421), and the `propertyConstraints`
rule `rewardSplitIsWhole` refuses a split that does not add up to 100 with
`DocumentPropertyConstraintViolatedError` (10422). The cap on additions is the
exception (see above). `SubmittedCharter::from_document_properties` in
`rs-dpp` (`packages/rs-dpp/src/moderation_charter/`) only reads a proposal,
without reading state:
exception (see above), and the settle a team change forces is an effect of the
change, not a rule on it (see [Seating](#seating)).
`SubmittedCharter::from_document_properties` in `rs-dpp`
(`packages/rs-dpp/src/moderation_charter/`) only reads a proposal, without
reading state:

| Rule | Error | Code |
| --- | --- | --- |
Expand Down
31 changes: 31 additions & 0 deletions packages/dapi-grpc/clients/drive/v0/nodejs/drive_pbjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24311,6 +24311,7 @@ $root.org = (function() {
* @property {number|null} [code] ContractModerationReason code
* @property {string|null} [text] ContractModerationReason text
* @property {Array.<org.dash.platform.dapi.v0.IContractModerationDocument>|null} [documents] ContractModerationReason documents
* @property {Uint8Array|null} [reasonDocumentId] ContractModerationReason reasonDocumentId
*/

/**
Expand Down Expand Up @@ -24353,6 +24354,14 @@ $root.org = (function() {
*/
ContractModerationReason.prototype.documents = $util.emptyArray;

/**
* ContractModerationReason reasonDocumentId.
* @member {Uint8Array} reasonDocumentId
* @memberof org.dash.platform.dapi.v0.ContractModerationReason
* @instance
*/
ContractModerationReason.prototype.reasonDocumentId = $util.newBuffer([]);

/**
* Creates a new ContractModerationReason instance using the specified properties.
* @function create
Expand Down Expand Up @@ -24384,6 +24393,8 @@ $root.org = (function() {
if (message.documents != null && message.documents.length)
for (var i = 0; i < message.documents.length; ++i)
$root.org.dash.platform.dapi.v0.ContractModerationDocument.encode(message.documents[i], writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim();
if (message.reasonDocumentId != null && Object.hasOwnProperty.call(message, "reasonDocumentId"))
writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.reasonDocumentId);
return writer;
};

Expand Down Expand Up @@ -24429,6 +24440,9 @@ $root.org = (function() {
message.documents = [];
message.documents.push($root.org.dash.platform.dapi.v0.ContractModerationDocument.decode(reader, reader.uint32()));
break;
case 4:
message.reasonDocumentId = reader.bytes();
break;
default:
reader.skipType(tag & 7);
break;
Expand Down Expand Up @@ -24479,6 +24493,9 @@ $root.org = (function() {
return "documents." + error;
}
}
if (message.reasonDocumentId != null && message.hasOwnProperty("reasonDocumentId"))
if (!(message.reasonDocumentId && typeof message.reasonDocumentId.length === "number" || $util.isString(message.reasonDocumentId)))
return "reasonDocumentId: buffer expected";
return null;
};

Expand Down Expand Up @@ -24508,6 +24525,11 @@ $root.org = (function() {
message.documents[i] = $root.org.dash.platform.dapi.v0.ContractModerationDocument.fromObject(object.documents[i]);
}
}
if (object.reasonDocumentId != null)
if (typeof object.reasonDocumentId === "string")
$util.base64.decode(object.reasonDocumentId, message.reasonDocumentId = $util.newBuffer($util.base64.length(object.reasonDocumentId)), 0);
else if (object.reasonDocumentId.length >= 0)
message.reasonDocumentId = object.reasonDocumentId;
return message;
};

Expand All @@ -24529,6 +24551,13 @@ $root.org = (function() {
if (options.defaults) {
object.code = 0;
object.text = "";
if (options.bytes === String)
object.reasonDocumentId = "";
else {
object.reasonDocumentId = [];
if (options.bytes !== Array)
object.reasonDocumentId = $util.newBuffer(object.reasonDocumentId);
}
}
if (message.code != null && message.hasOwnProperty("code"))
object.code = message.code;
Expand All @@ -24539,6 +24568,8 @@ $root.org = (function() {
for (var j = 0; j < message.documents.length; ++j)
object.documents[j] = $root.org.dash.platform.dapi.v0.ContractModerationDocument.toObject(message.documents[j], options);
}
if (message.reasonDocumentId != null && message.hasOwnProperty("reasonDocumentId"))
object.reasonDocumentId = options.bytes === String ? $util.base64.encode(message.reasonDocumentId, 0, message.reasonDocumentId.length) : options.bytes === Array ? Array.prototype.slice.call(message.reasonDocumentId) : message.reasonDocumentId;
return object;
};

Expand Down
31 changes: 31 additions & 0 deletions packages/dapi-grpc/clients/platform/v0/nodejs/platform_pbjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -23803,6 +23803,7 @@ $root.org = (function() {
* @property {number|null} [code] ContractModerationReason code
* @property {string|null} [text] ContractModerationReason text
* @property {Array.<org.dash.platform.dapi.v0.IContractModerationDocument>|null} [documents] ContractModerationReason documents
* @property {Uint8Array|null} [reasonDocumentId] ContractModerationReason reasonDocumentId
*/

/**
Expand Down Expand Up @@ -23845,6 +23846,14 @@ $root.org = (function() {
*/
ContractModerationReason.prototype.documents = $util.emptyArray;

/**
* ContractModerationReason reasonDocumentId.
* @member {Uint8Array} reasonDocumentId
* @memberof org.dash.platform.dapi.v0.ContractModerationReason
* @instance
*/
ContractModerationReason.prototype.reasonDocumentId = $util.newBuffer([]);

/**
* Creates a new ContractModerationReason instance using the specified properties.
* @function create
Expand Down Expand Up @@ -23876,6 +23885,8 @@ $root.org = (function() {
if (message.documents != null && message.documents.length)
for (var i = 0; i < message.documents.length; ++i)
$root.org.dash.platform.dapi.v0.ContractModerationDocument.encode(message.documents[i], writer.uint32(/* id 3, wireType 2 =*/26).fork()).ldelim();
if (message.reasonDocumentId != null && Object.hasOwnProperty.call(message, "reasonDocumentId"))
writer.uint32(/* id 4, wireType 2 =*/34).bytes(message.reasonDocumentId);
return writer;
};

Expand Down Expand Up @@ -23921,6 +23932,9 @@ $root.org = (function() {
message.documents = [];
message.documents.push($root.org.dash.platform.dapi.v0.ContractModerationDocument.decode(reader, reader.uint32()));
break;
case 4:
message.reasonDocumentId = reader.bytes();
break;
default:
reader.skipType(tag & 7);
break;
Expand Down Expand Up @@ -23971,6 +23985,9 @@ $root.org = (function() {
return "documents." + error;
}
}
if (message.reasonDocumentId != null && message.hasOwnProperty("reasonDocumentId"))
if (!(message.reasonDocumentId && typeof message.reasonDocumentId.length === "number" || $util.isString(message.reasonDocumentId)))
return "reasonDocumentId: buffer expected";
return null;
};

Expand Down Expand Up @@ -24000,6 +24017,11 @@ $root.org = (function() {
message.documents[i] = $root.org.dash.platform.dapi.v0.ContractModerationDocument.fromObject(object.documents[i]);
}
}
if (object.reasonDocumentId != null)
if (typeof object.reasonDocumentId === "string")
$util.base64.decode(object.reasonDocumentId, message.reasonDocumentId = $util.newBuffer($util.base64.length(object.reasonDocumentId)), 0);
else if (object.reasonDocumentId.length >= 0)
message.reasonDocumentId = object.reasonDocumentId;
return message;
};

Expand All @@ -24021,6 +24043,13 @@ $root.org = (function() {
if (options.defaults) {
object.code = 0;
object.text = "";
if (options.bytes === String)
object.reasonDocumentId = "";
else {
object.reasonDocumentId = [];
if (options.bytes !== Array)
object.reasonDocumentId = $util.newBuffer(object.reasonDocumentId);
}
}
if (message.code != null && message.hasOwnProperty("code"))
object.code = message.code;
Expand All @@ -24031,6 +24060,8 @@ $root.org = (function() {
for (var j = 0; j < message.documents.length; ++j)
object.documents[j] = $root.org.dash.platform.dapi.v0.ContractModerationDocument.toObject(message.documents[j], options);
}
if (message.reasonDocumentId != null && message.hasOwnProperty("reasonDocumentId"))
object.reasonDocumentId = options.bytes === String ? $util.base64.encode(message.reasonDocumentId, 0, message.reasonDocumentId.length) : options.bytes === Array ? Array.prototype.slice.call(message.reasonDocumentId) : message.reasonDocumentId;
return object;
};

Expand Down
Loading
Loading