drive/state_transition_action/batch/v0/
mod.rs1use crate::state_transition_action::batch::batched_transition::document_transition::document_base_transition_action::DocumentBaseTransitionActionAccessorsV0;
2use crate::state_transition_action::batch::{
3 GasPayer, ResolvedContractGroupMemberships, ResolvedGasSponsor,
4};
5use crate::state_transition_action::contract::moderators_pot_settlement::ModeratorsPotSettlement;
6use dpp::prelude::FeeMultiplier;
7use dpp::consensus::state::document::document_action_fee_agreement_mismatch_error::DocumentActionFeeAgreementMismatchError;
8use dpp::consensus::state::document::document_action_fee_agreement_not_set_error::DocumentActionFeeAgreementNotSetError;
9use dpp::consensus::state::document::document_action_fee_moderators_share_mismatch_error::DocumentActionFeeModeratorsShareMismatchError;
10use dpp::moderation_charter::moderators_share_of;
11use dpp::consensus::state::document::document_action_fee_multiplier_not_tolerated_error::DocumentActionFeeMultiplierNotToleratedError;
12use dpp::consensus::state::token::{GasFeesPaidByNotAllowedError, InconsistentGasFeesPaidByInBatchError};
13use dpp::consensus::ConsensusError;
14use dpp::data_contract::accessors::v0::DataContractV0Getters;
15use dpp::tokens::gas_fees_paid_by::GasFeesPaidBy;
16use dpp::fee::Credits;
17use std::collections::{BTreeMap, BTreeSet};
18use crate::state_transition_action::batch::batched_transition::document_transition::DocumentTransitionAction;
19use dpp::identifier::Identifier;
20use dpp::prelude::UserFeeIncrease;
21use dpp::ProtocolError;
22use crate::state_transition_action::batch::batched_transition::BatchedTransitionAction;
23use crate::state_transition_action::batch::batched_transition::document_transition::document_create_transition_action::DocumentCreateTransitionActionAccessorsV0;
24use crate::state_transition_action::batch::batched_transition::document_transition::document_purchase_transition_action::DocumentPurchaseTransitionActionAccessorsV0;
25use crate::state_transition_action::batch::batched_transition::token_transition::token_direct_purchase_transition_action::TokenDirectPurchaseTransitionActionAccessorsV0;
26use crate::state_transition_action::batch::batched_transition::token_transition::token_direct_purchase_to_pool_transition_action::TokenDirectPurchaseToPoolTransitionActionAccessorsV0;
27use crate::state_transition_action::batch::batched_transition::token_transition::TokenTransitionAction;
28
29#[derive(Default, Debug, Clone)]
31pub struct BatchTransitionActionV0 {
32 pub owner_id: Identifier,
34 pub transitions: Vec<BatchedTransitionAction>,
36 pub user_fee_increase: UserFeeIncrease,
38 pub contract_group_memberships: BTreeMap<Identifier, ResolvedContractGroupMemberships>,
43 pub gas_sponsor: Option<ResolvedGasSponsor>,
46 pub action_fee_multiplier_permille: Option<FeeMultiplier>,
51 pub seated_moderators_shares: BTreeMap<Identifier, Option<u8>>,
57
58 pub lapsed_suspensions: BTreeSet<Identifier>,
63
64 pub moderators_pot_settlements: Vec<ModeratorsPotSettlement>,
71}
72
73impl BatchTransitionActionV0 {
74 pub(in crate::state_transition_action) fn validate_action_fee_agreements(
76 &self,
77 ) -> Result<Result<(), ConsensusError>, ProtocolError> {
78 for transition in &self.transitions {
79 let BatchedTransitionAction::DocumentAction(document_action) = transition else {
80 continue;
81 };
82 let base = document_action.base();
83 let Some(declared) = base.declared_action_fee_with_agreement() else {
84 continue;
85 };
86 let document_type_name = || base.document_type_name().clone();
87 let action = || document_action.action_name().to_string();
88 let Some(agreement) = declared.agreement else {
89 return Ok(Err(DocumentActionFeeAgreementNotSetError::new(
90 document_type_name(),
91 action(),
92 declared.pricing,
93 declared.fee,
94 )
95 .into()));
96 };
97 if base.agrees_to_a_moderators_discount() {
98 let moderators_share = *self
102 .seated_moderators_shares
103 .get(&base.data_contract_id())
104 .ok_or(ProtocolError::CorruptedCodeExecution(
105 "the batch transformer reads the seated moderators share of every \
106 contract a document transition agrees to a discount on"
107 .to_string(),
108 ))?;
109 let offered = moderators_share
110 .map(|share| moderators_share_of(declared.fee.moderators, share));
111 if offered != Some(agreement.moderators()) {
112 return Ok(Err(DocumentActionFeeModeratorsShareMismatchError::new(
113 document_type_name(),
114 action(),
115 declared.fee.moderators,
116 agreement.moderators(),
117 moderators_share,
118 )
119 .into()));
120 }
121 } else if !agreement.matches_declared(declared.pricing, declared.fee) {
122 return Ok(Err(DocumentActionFeeAgreementMismatchError::new(
123 document_type_name(),
124 action(),
125 declared.pricing,
126 declared.fee,
127 &agreement,
128 )
129 .into()));
130 }
131 if let Some(agreed_fee_multiplier) = agreement.fee_multiplier() {
133 let current = self.action_fee_multiplier_permille.ok_or(
134 ProtocolError::CorruptedCodeExecution(
135 "the batch transformer reads the fee multiplier of every batch that \
136 declares an action fee priced by it"
137 .to_string(),
138 ),
139 )?;
140 if !agreed_fee_multiplier.tolerates(current) {
141 return Ok(Err(DocumentActionFeeMultiplierNotToleratedError::new(
142 document_type_name(),
143 action(),
144 agreed_fee_multiplier,
145 current,
146 )
147 .into()));
148 }
149 }
150 }
151 Ok(Ok(()))
152 }
153
154 pub(in crate::state_transition_action) fn resolve_gas_payer(
156 &self,
157 ) -> Result<GasPayer, ConsensusError> {
158 let mut payer: Option<Option<Identifier>> = None;
160 let mut strict = false;
161 for transition in &self.transitions {
162 let transition_payer = match transition {
163 BatchedTransitionAction::DocumentAction(document_action) => {
164 let base = document_action.base();
165 let requested = base.gas_fees_paid_by();
166 let offered = base.contract_gas_fees_paid_by();
167 match GasFeesPaidBy::resolve(offered, requested) {
168 None => {
169 return Err(GasFeesPaidByNotAllowedError::new(
170 base.document_type_name().clone(),
171 document_action.action_name().to_string(),
172 requested,
173 offered,
174 )
175 .into())
176 }
177 Some(GasFeesPaidBy::DocumentOwner) => None,
178 Some(GasFeesPaidBy::PreferContractOwner) => {
179 Some(base.data_contract_fetch_info_ref().contract.owner_id())
180 }
181 Some(GasFeesPaidBy::ContractOwner) => {
182 strict = true;
183 Some(base.data_contract_fetch_info_ref().contract.owner_id())
184 }
185 }
186 }
187 BatchedTransitionAction::TokenAction(_) => None,
189 BatchedTransitionAction::BumpIdentityDataContractNonce(_) => continue,
193 };
194 match payer {
195 None => payer = Some(transition_payer),
196 Some(expected) if expected != transition_payer => {
197 return Err(InconsistentGasFeesPaidByInBatchError::new(
198 expected,
199 transition_payer,
200 )
201 .into())
202 }
203 Some(_) => {}
204 }
205 }
206 Ok(match payer.flatten() {
207 None => GasPayer::DocumentOwner,
208 Some(identity_id) => GasPayer::ContractOwner {
209 identity_id,
210 strict,
211 },
212 })
213 }
214
215 pub(in crate::state_transition_action) fn all_used_balances(
216 &self,
217 ) -> Result<Option<Credits>, ProtocolError> {
218 Ok(match (self.all_purchases_amount()?, self.all_conflicting_index_collateral_voting_funds()?) {
219 (Some(all_purchases_amount), Some(all_conflicting_index_collateral_voting_funds)) => Some(all_purchases_amount.checked_add(all_conflicting_index_collateral_voting_funds).ok_or(ProtocolError::Overflow("overflow between all_purchases_amount and all_conflicting_index_collateral_voting_funds"))?),
220 (Some(all_purchases_amount), None) => Some(all_purchases_amount),
221 (None, Some(all_conflicting_index_collateral_voting_funds)) => Some(all_conflicting_index_collateral_voting_funds),
222 (None, None) => None,
223 })
224 }
225 pub(in crate::state_transition_action) fn all_purchases_amount(
226 &self,
227 ) -> Result<Option<Credits>, ProtocolError> {
228 let (total, any_purchases): (Option<Credits>, bool) = self
229 .transitions
230 .iter()
231 .filter_map(|transition| match transition {
232 BatchedTransitionAction::DocumentAction(
233 DocumentTransitionAction::PurchaseAction(document_purchase),
234 ) => Some(document_purchase.price()),
235 BatchedTransitionAction::TokenAction(
236 TokenTransitionAction::DirectPurchaseAction(token_purchase),
237 ) => Some(token_purchase.total_agreed_price()),
238 BatchedTransitionAction::TokenAction(
239 TokenTransitionAction::DirectPurchaseToPoolAction(token_purchase),
244 ) => Some(token_purchase.total_agreed_price()),
245 _ => None,
246 })
247 .fold((None, false), |(acc, _), price| match acc {
248 Some(acc_val) => acc_val
249 .checked_add(price)
250 .map_or((None, true), |sum| (Some(sum), true)),
251 None => (Some(price), true),
252 });
253
254 match (total, any_purchases) {
255 (Some(total), _) => Ok(Some(total)),
256 (None, true) => Err(ProtocolError::Overflow("overflow in all purchases amount")), _ => Ok(None), }
259 }
260
261 pub(in crate::state_transition_action) fn all_conflicting_index_collateral_voting_funds(
262 &self,
263 ) -> Result<Option<Credits>, ProtocolError> {
264 let (total, any_voting_funds): (Option<Credits>, bool) = self
265 .transitions
266 .iter()
267 .filter_map(|transition| match transition {
268 BatchedTransitionAction::DocumentAction(
269 DocumentTransitionAction::CreateAction(document_create_transition_action),
270 ) => document_create_transition_action
271 .prefunded_voting_balance()
272 .iter()
273 .try_fold(0u64, |acc, &(_, val)| acc.checked_add(val)),
274 _ => None,
275 })
276 .fold((None, false), |(acc, _), price| match acc {
277 Some(acc_val) => acc_val
278 .checked_add(price)
279 .map_or((None, true), |sum| (Some(sum), true)),
280 None => (Some(price), true),
281 });
282
283 match (total, any_voting_funds) {
284 (Some(total), _) => Ok(Some(total)),
285 (None, true) => Err(ProtocolError::Overflow(
286 "overflow in all voting funds amount",
287 )), _ => Ok(None),
289 }
290 }
291}