1use alloc::vec::Vec;
2use core::fmt::{self, Display, Formatter};
3
4#[cfg(feature = "datasize")]
5use datasize::DataSize;
6#[cfg(any(feature = "testing", test))]
7use rand::Rng;
8#[cfg(feature = "json-schema")]
9use schemars::JsonSchema;
10use serde::{Deserialize, Serialize};
11
12#[cfg(doc)]
13use super::Transaction;
14use super::{
15 serialization::CalltableSerializationEnvelope, InvalidTransaction, InvalidTransactionV1,
16 TransactionEntryPoint,
17};
18#[cfg(any(feature = "testing", test))]
19use crate::testing::TestRng;
20use crate::{
21 bytesrepr::{
22 Error::{self, Formatting},
23 FromBytes, ToBytes,
24 },
25 transaction::serialization::CalltableSerializationEnvelopeBuilder,
26 Digest,
27};
28#[cfg(any(feature = "std", test))]
29use crate::{Chainspec, Gas, Motes};
30
31#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
33#[cfg_attr(feature = "datasize", derive(DataSize))]
34#[cfg_attr(
35 feature = "json-schema",
36 derive(JsonSchema),
37 schemars(description = "Pricing mode of a Transaction.")
38)]
39#[serde(deny_unknown_fields)]
40pub enum PricingMode {
41 PaymentLimited {
44 payment_amount: u64,
46 gas_price_tolerance: u8,
50 standard_payment: bool,
52 },
53 Fixed {
56 additional_computation_factor: u8,
64 gas_price_tolerance: u8,
68 },
69 Prepaid {
72 receipt: Digest,
74 },
75}
76
77impl PricingMode {
78 #[cfg(any(feature = "testing", test))]
80 pub fn random(rng: &mut TestRng) -> Self {
81 match rng.gen_range(0..=2) {
82 0 => PricingMode::PaymentLimited {
83 payment_amount: rng.gen(),
84 gas_price_tolerance: 1,
85 standard_payment: true,
86 },
87 1 => PricingMode::Fixed {
88 gas_price_tolerance: rng.gen(),
89 additional_computation_factor: 1,
90 },
91 2 => PricingMode::Prepaid { receipt: rng.gen() },
92 _ => unreachable!(),
93 }
94 }
95
96 pub fn is_standard_payment(&self) -> bool {
98 match self {
99 PricingMode::PaymentLimited {
100 standard_payment, ..
101 } => *standard_payment,
102 PricingMode::Fixed { .. } => true,
103 PricingMode::Prepaid { .. } => true,
104 }
105 }
106
107 fn serialized_field_lengths(&self) -> Vec<usize> {
108 match self {
109 PricingMode::PaymentLimited {
110 payment_amount,
111 gas_price_tolerance,
112 standard_payment,
113 } => {
114 vec![
115 crate::bytesrepr::U8_SERIALIZED_LENGTH,
116 payment_amount.serialized_length(),
117 gas_price_tolerance.serialized_length(),
118 standard_payment.serialized_length(),
119 ]
120 }
121 PricingMode::Fixed {
122 gas_price_tolerance,
123 additional_computation_factor,
124 } => {
125 vec![
126 crate::bytesrepr::U8_SERIALIZED_LENGTH,
127 gas_price_tolerance.serialized_length(),
128 additional_computation_factor.serialized_length(),
129 ]
130 }
131 PricingMode::Prepaid { receipt } => {
132 vec![
133 crate::bytesrepr::U8_SERIALIZED_LENGTH,
134 receipt.serialized_length(),
135 ]
136 }
137 }
138 }
139
140 #[cfg(any(feature = "std", test))]
141 pub fn gas_limit(&self, chainspec: &Chainspec, lane_id: u8) -> Result<Gas, PricingModeError> {
143 let gas = match self {
144 PricingMode::PaymentLimited { payment_amount, .. } => Gas::new(*payment_amount),
145 PricingMode::Fixed { .. } => {
146 Gas::new(chainspec.get_max_gas_limit_by_category(lane_id))
148 }
149 PricingMode::Prepaid { receipt } => {
150 return Err(PricingModeError::InvalidPricingMode {
151 price_mode: PricingMode::Prepaid { receipt: *receipt },
152 });
153 }
154 };
155 Ok(gas)
156 }
157
158 #[cfg(any(feature = "std", test))]
159 pub fn gas_cost(
161 &self,
162 chainspec: &Chainspec,
163 lane_id: u8,
164 gas_price: u8,
165 ) -> Result<Motes, PricingModeError> {
166 let gas_limit = self.gas_limit(chainspec, lane_id)?;
167 let motes = match self {
168 PricingMode::PaymentLimited { payment_amount, .. } => {
169 Motes::from_gas(Gas::from(*payment_amount), gas_price)
170 .ok_or(PricingModeError::UnableToCalculateGasCost)?
171 }
172 PricingMode::Fixed { .. } => Motes::from_gas(gas_limit, gas_price)
173 .ok_or(PricingModeError::UnableToCalculateGasCost)?,
174 PricingMode::Prepaid { .. } => {
175 Motes::zero() }
177 };
178 Ok(motes)
179 }
180
181 pub fn additional_computation_factor(&self) -> u8 {
183 match self {
184 PricingMode::PaymentLimited { .. } => 0,
185 PricingMode::Fixed {
186 additional_computation_factor,
187 ..
188 } => *additional_computation_factor,
189 PricingMode::Prepaid { .. } => 0,
190 }
191 }
192}
193
194#[cfg(any(feature = "testing", test))]
198impl Default for PricingMode {
199 fn default() -> Self {
200 Self::PaymentLimited {
201 payment_amount: 1,
202 gas_price_tolerance: 1,
203 standard_payment: true,
204 }
205 }
206}
207
208#[derive(Debug)]
210pub enum PricingModeError {
211 EntryPointCannotBeCall,
213 EntryPointCannotBeCustom {
215 entry_point: TransactionEntryPoint,
217 },
218 InvalidPricingMode {
220 price_mode: PricingMode,
222 },
223 UnableToCalculateGasCost,
225 UnexpectedEntryPoint {
227 entry_point: TransactionEntryPoint,
228 lane_id: u8,
229 },
230}
231
232impl From<PricingModeError> for InvalidTransaction {
233 fn from(err: PricingModeError) -> Self {
234 InvalidTransaction::V1(err.into())
235 }
236}
237
238impl From<PricingModeError> for InvalidTransactionV1 {
239 fn from(err: PricingModeError) -> Self {
240 match err {
241 PricingModeError::EntryPointCannotBeCall => {
242 InvalidTransactionV1::EntryPointCannotBeCall
243 }
244 PricingModeError::EntryPointCannotBeCustom { entry_point } => {
245 InvalidTransactionV1::EntryPointCannotBeCustom { entry_point }
246 }
247 PricingModeError::InvalidPricingMode { price_mode } => {
248 InvalidTransactionV1::InvalidPricingMode { price_mode }
249 }
250 PricingModeError::UnableToCalculateGasCost => {
251 InvalidTransactionV1::UnableToCalculateGasCost
252 }
253 PricingModeError::UnexpectedEntryPoint {
254 entry_point,
255 lane_id,
256 } => InvalidTransactionV1::UnexpectedEntryPoint {
257 entry_point,
258 lane_id,
259 },
260 }
261 }
262}
263const TAG_FIELD_INDEX: u16 = 0;
264
265const PAYMENT_LIMITED_VARIANT_TAG: u8 = 0;
266const PAYMENT_LIMITED_PAYMENT_AMOUNT_INDEX: u16 = 1;
267const PAYMENT_LIMITED_GAS_PRICE_TOLERANCE_INDEX: u16 = 2;
268const PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX: u16 = 3;
269
270const FIXED_VARIANT_TAG: u8 = 1;
271const FIXED_GAS_PRICE_TOLERANCE_INDEX: u16 = 1;
272const FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX: u16 = 2;
273
274const RESERVED_VARIANT_TAG: u8 = 2;
275const RESERVED_RECEIPT_INDEX: u16 = 1;
276
277impl ToBytes for PricingMode {
278 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
279 match self {
280 PricingMode::PaymentLimited {
281 payment_amount,
282 gas_price_tolerance,
283 standard_payment,
284 } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
285 .add_field(TAG_FIELD_INDEX, &PAYMENT_LIMITED_VARIANT_TAG)?
286 .add_field(PAYMENT_LIMITED_PAYMENT_AMOUNT_INDEX, &payment_amount)?
287 .add_field(
288 PAYMENT_LIMITED_GAS_PRICE_TOLERANCE_INDEX,
289 &gas_price_tolerance,
290 )?
291 .add_field(PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX, &standard_payment)?
292 .binary_payload_bytes(),
293 PricingMode::Fixed {
294 gas_price_tolerance,
295 additional_computation_factor,
296 } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
297 .add_field(TAG_FIELD_INDEX, &FIXED_VARIANT_TAG)?
298 .add_field(FIXED_GAS_PRICE_TOLERANCE_INDEX, &gas_price_tolerance)?
299 .add_field(
300 FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX,
301 &additional_computation_factor,
302 )?
303 .binary_payload_bytes(),
304 PricingMode::Prepaid { receipt } => {
305 CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())?
306 .add_field(TAG_FIELD_INDEX, &RESERVED_VARIANT_TAG)?
307 .add_field(RESERVED_RECEIPT_INDEX, &receipt)?
308 .binary_payload_bytes()
309 }
310 }
311 }
312 fn serialized_length(&self) -> usize {
313 CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths())
314 }
315}
316
317impl FromBytes for PricingMode {
318 fn from_bytes(bytes: &[u8]) -> Result<(PricingMode, &[u8]), Error> {
319 let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(4, bytes)?;
320 let window = binary_payload.start_consuming()?.ok_or(Formatting)?;
321 window.verify_index(TAG_FIELD_INDEX)?;
322 let (tag, window) = window.deserialize_and_maybe_next::<u8>()?;
323 let to_ret = match tag {
324 PAYMENT_LIMITED_VARIANT_TAG => {
325 let window = window.ok_or(Formatting)?;
326 window.verify_index(PAYMENT_LIMITED_PAYMENT_AMOUNT_INDEX)?;
327 let (payment_amount, window) = window.deserialize_and_maybe_next::<u64>()?;
328 let window = window.ok_or(Formatting)?;
329 window.verify_index(PAYMENT_LIMITED_GAS_PRICE_TOLERANCE_INDEX)?;
330 let (gas_price_tolerance, window) = window.deserialize_and_maybe_next::<u8>()?;
331 let window = window.ok_or(Formatting)?;
332 window.verify_index(PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX)?;
333 let (standard_payment, window) = window.deserialize_and_maybe_next::<bool>()?;
334 if window.is_some() {
335 return Err(Formatting);
336 }
337 Ok(PricingMode::PaymentLimited {
338 payment_amount,
339 gas_price_tolerance,
340 standard_payment,
341 })
342 }
343 FIXED_VARIANT_TAG => {
344 let window = window.ok_or(Formatting)?;
345 window.verify_index(FIXED_GAS_PRICE_TOLERANCE_INDEX)?;
346 let (gas_price_tolerance, window) = window.deserialize_and_maybe_next::<u8>()?;
347 let window = window.ok_or(Formatting)?;
348 window.verify_index(FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX)?;
349 let (additional_computation_factor, window) =
350 window.deserialize_and_maybe_next::<u8>()?;
351 if window.is_some() {
352 return Err(Formatting);
353 }
354 Ok(PricingMode::Fixed {
355 gas_price_tolerance,
356 additional_computation_factor,
357 })
358 }
359 RESERVED_VARIANT_TAG => {
360 let window = window.ok_or(Formatting)?;
361 window.verify_index(RESERVED_RECEIPT_INDEX)?;
362 let (receipt, window) = window.deserialize_and_maybe_next::<Digest>()?;
363 if window.is_some() {
364 return Err(Formatting);
365 }
366 Ok(PricingMode::Prepaid { receipt })
367 }
368 _ => Err(Formatting),
369 };
370 to_ret.map(|endpoint| (endpoint, remainder))
371 }
372}
373
374impl Display for PricingMode {
375 fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
376 match self {
377 PricingMode::PaymentLimited {
378 payment_amount,
379 gas_price_tolerance: gas_price,
380 standard_payment,
381 } => {
382 write!(
383 formatter,
384 "payment amount {}, gas price multiplier {} standard_payment {}",
385 payment_amount, gas_price, standard_payment
386 )
387 }
388 PricingMode::Prepaid { receipt } => write!(formatter, "prepaid: {}", receipt),
389 PricingMode::Fixed {
390 gas_price_tolerance,
391 additional_computation_factor,
392 } => write!(
393 formatter,
394 "fixed pricing {} {}",
395 gas_price_tolerance, additional_computation_factor
396 ),
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404 use crate::bytesrepr;
405
406 #[test]
407 fn test_to_bytes_and_from_bytes() {
408 bytesrepr::test_serialization_roundtrip(&PricingMode::PaymentLimited {
409 payment_amount: 100,
410 gas_price_tolerance: 1,
411 standard_payment: true,
412 });
413 bytesrepr::test_serialization_roundtrip(&PricingMode::Fixed {
414 gas_price_tolerance: 2,
415 additional_computation_factor: 1,
416 });
417 bytesrepr::test_serialization_roundtrip(&PricingMode::Prepaid {
418 receipt: Digest::hash(b"prepaid"),
419 });
420 }
421
422 use crate::gens::pricing_mode_arb;
423 use proptest::prelude::*;
424 proptest! {
425 #[test]
426 fn generative_bytesrepr_roundtrip(val in pricing_mode_arb()) {
427 bytesrepr::test_serialization_roundtrip(&val);
428 }
429 }
430}