penumbra_sdk_fee/component/
fee_pay.rs1use anyhow::{ensure, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use penumbra_sdk_asset::Value;
5use penumbra_sdk_proto::core::component::fee::v1 as pb;
6use penumbra_sdk_proto::state::StateWriteProto as _;
7
8use crate::{Fee, Gas};
9
10use super::view::{StateReadExt, StateWriteExt};
11
12#[async_trait]
14pub trait FeePay: StateWrite {
15 async fn pay_fee(&mut self, gas_used: Gas, fee: Fee) -> Result<()> {
17 let current_gas_prices = if fee.asset_id() == *penumbra_sdk_asset::STAKING_TOKEN_ASSET_ID {
18 self.get_gas_prices()
19 .await
20 .expect("gas prices must be present in state")
21 } else {
22 let alt_gas_prices = self
23 .get_alt_gas_prices()
24 .await
25 .expect("alt gas prices must be present in state");
26 alt_gas_prices
29 .into_iter()
30 .find(|prices| prices.asset_id == fee.asset_id())
31 .ok_or_else(|| {
32 anyhow::anyhow!("fee token {} not recognized by the chain", fee.asset_id())
33 })?
34 };
35
36 ensure!(
38 current_gas_prices.asset_id == fee.asset_id(),
39 "unexpected mismatch between fee and queried gas prices (expected: {}, found: {})",
40 fee.asset_id(),
41 current_gas_prices.asset_id,
42 );
43
44 let base_fee = current_gas_prices.fee(&gas_used);
46
47 ensure!(
49 fee.amount() >= base_fee.amount(),
50 "fee must be greater than or equal to the transaction base price (supplied: {}, base: {})",
51 fee.amount(),
52 base_fee.amount(),
53 );
54
55 let tip = Fee(Value {
57 amount: fee.amount() - base_fee.amount(),
58 asset_id: fee.asset_id(),
59 });
60
61 self.record_proto(pb::EventPaidFee {
63 fee: Some(fee.into()),
64 base_fee: Some(base_fee.into()),
65 gas_used: Some(gas_used.into()),
66 tip: Some(tip.into()),
67 });
68
69 self.raw_accumulate_base_fee(base_fee);
71 self.raw_accumulate_tip(tip);
72
73 Ok(())
74 }
75}
76
77impl<S: StateWrite + ?Sized> FeePay for S {}