hedera/token/
token_fee_schedule_update_transaction.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use hedera_proto::services;
4use hedera_proto::services::token_service_client::TokenServiceClient;
5use tonic::transport::Channel;
6
7use crate::protobuf::{
8    FromProtobuf,
9    ToProtobuf,
10};
11use crate::token::custom_fees::AnyCustomFee;
12use crate::transaction::{
13    AnyTransactionData,
14    ChunkInfo,
15    ToSchedulableTransactionDataProtobuf,
16    ToTransactionDataProtobuf,
17    TransactionData,
18    TransactionExecute,
19};
20use crate::{
21    BoxGrpcFuture,
22    Error,
23    TokenId,
24    Transaction,
25    ValidateChecksums,
26};
27
28/// At consensus, updates a token type's fee schedule to the given list of custom fees.
29///
30/// If the target token type has no `fee_schedule_key`, resolves to `TokenHasNoFeeScheduleKey`.
31/// Otherwise this transaction must be signed to the `fee_schedule_key`, or the transaction will
32/// resolve to `InvalidSignature`.
33///
34/// If the `custom_fees` list is empty, clears the fee schedule or resolves to
35/// `CustomScheduleAlreadyHasNoFees` if the fee schedule was already empty.
36pub type TokenFeeScheduleUpdateTransaction = Transaction<TokenFeeScheduleUpdateTransactionData>;
37
38#[derive(Debug, Clone, Default)]
39pub struct TokenFeeScheduleUpdateTransactionData {
40    /// The token whose fee schedule is to be updated.
41    token_id: Option<TokenId>,
42
43    /// The new custom fees to be assessed during a transfer.
44    custom_fees: Vec<AnyCustomFee>,
45}
46
47impl TokenFeeScheduleUpdateTransaction {
48    /// Returns the ID of the token that's being updated.
49    #[must_use]
50    pub fn get_token_id(&self) -> Option<TokenId> {
51        self.data().token_id
52    }
53
54    // note(sr): what is being updated is implicit.
55    /// Sets the ID of the token that's being updated.
56    pub fn token_id(&mut self, token_id: impl Into<TokenId>) -> &mut Self {
57        self.data_mut().token_id = Some(token_id.into());
58        self
59    }
60
61    /// Returns the new custom fees to be assessed during a transfer.
62    #[must_use]
63    pub fn get_custom_fees(&self) -> &[AnyCustomFee] {
64        &self.data().custom_fees
65    }
66
67    /// Sets the new custom fees to be assessed during a transfer.
68    pub fn custom_fees(
69        &mut self,
70        custom_fees: impl IntoIterator<Item = AnyCustomFee>,
71    ) -> &mut Self {
72        self.data_mut().custom_fees = custom_fees.into_iter().collect();
73        self
74    }
75}
76
77impl TransactionData for TokenFeeScheduleUpdateTransactionData {}
78
79impl TransactionExecute for TokenFeeScheduleUpdateTransactionData {
80    fn execute(
81        &self,
82        channel: Channel,
83        request: services::Transaction,
84    ) -> BoxGrpcFuture<'_, services::TransactionResponse> {
85        Box::pin(async {
86            TokenServiceClient::new(channel).update_token_fee_schedule(request).await
87        })
88    }
89}
90
91impl ValidateChecksums for TokenFeeScheduleUpdateTransactionData {
92    fn validate_checksums(&self, ledger_id: &crate::ledger_id::RefLedgerId) -> Result<(), Error> {
93        // TODO: validate custom fees (they need an impl)
94        self.token_id.validate_checksums(ledger_id)
95    }
96}
97
98impl ToTransactionDataProtobuf for TokenFeeScheduleUpdateTransactionData {
99    fn to_transaction_data_protobuf(
100        &self,
101        chunk_info: &ChunkInfo,
102    ) -> services::transaction_body::Data {
103        let _ = chunk_info.assert_single_transaction();
104
105        services::transaction_body::Data::TokenFeeScheduleUpdate(self.to_protobuf())
106    }
107}
108
109impl ToSchedulableTransactionDataProtobuf for TokenFeeScheduleUpdateTransactionData {
110    fn to_schedulable_transaction_data_protobuf(
111        &self,
112    ) -> services::schedulable_transaction_body::Data {
113        services::schedulable_transaction_body::Data::TokenFeeScheduleUpdate(self.to_protobuf())
114    }
115}
116
117impl From<TokenFeeScheduleUpdateTransactionData> for AnyTransactionData {
118    fn from(transaction: TokenFeeScheduleUpdateTransactionData) -> Self {
119        Self::TokenFeeScheduleUpdate(transaction)
120    }
121}
122
123impl FromProtobuf<services::TokenFeeScheduleUpdateTransactionBody>
124    for TokenFeeScheduleUpdateTransactionData
125{
126    fn from_protobuf(pb: services::TokenFeeScheduleUpdateTransactionBody) -> crate::Result<Self> {
127        Ok(Self {
128            token_id: Option::from_protobuf(pb.token_id)?,
129            custom_fees: Vec::from_protobuf(pb.custom_fees)?,
130        })
131    }
132}
133
134impl ToProtobuf for TokenFeeScheduleUpdateTransactionData {
135    type Protobuf = services::TokenFeeScheduleUpdateTransactionBody;
136
137    fn to_protobuf(&self) -> Self::Protobuf {
138        services::TokenFeeScheduleUpdateTransactionBody {
139            token_id: self.token_id.to_protobuf(),
140            custom_fees: self.custom_fees.to_protobuf(),
141        }
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use expect_test::expect;
148    use hedera_proto::services;
149
150    use crate::protobuf::{
151        FromProtobuf,
152        ToProtobuf,
153    };
154    use crate::token::TokenFeeScheduleUpdateTransactionData;
155    use crate::transaction::test_helpers::{
156        check_body,
157        transaction_body,
158    };
159    use crate::{
160        AnyCustomFee,
161        AnyTransaction,
162        FixedFee,
163        FractionalFee,
164        TokenFeeScheduleUpdateTransaction,
165        TokenId,
166    };
167
168    const TOKEN_ID: TokenId = TokenId::new(0, 0, 8798);
169
170    fn custom_fees() -> [AnyCustomFee; 2] {
171        [
172            FixedFee {
173                fee: crate::FixedFeeData {
174                    amount: 10,
175                    denominating_token_id: Some(TokenId::new(0, 0, 483902)),
176                },
177                fee_collector_account_id: Some("4322".parse().unwrap()),
178                all_collectors_are_exempt: false,
179            }
180            .into(),
181            FractionalFee {
182                fee: crate::FractionalFeeData {
183                    denominator: 7,
184                    numerator: 3,
185                    minimum_amount: 3,
186                    maximum_amount: 100,
187                    assessment_method: crate::FeeAssessmentMethod::Exclusive,
188                },
189                fee_collector_account_id: Some("389042".parse().unwrap()),
190                all_collectors_are_exempt: false,
191            }
192            .into(),
193        ]
194    }
195
196    fn make_transaction() -> TokenFeeScheduleUpdateTransaction {
197        let mut tx = TokenFeeScheduleUpdateTransaction::new_for_tests();
198
199        tx.token_id(TOKEN_ID).custom_fees(custom_fees()).freeze().unwrap();
200
201        tx
202    }
203
204    #[test]
205    fn serialize() {
206        let tx = make_transaction();
207
208        let tx = transaction_body(tx);
209
210        let tx = check_body(tx);
211
212        expect![[r#"
213            TokenFeeScheduleUpdate(
214                TokenFeeScheduleUpdateTransactionBody {
215                    token_id: Some(
216                        TokenId {
217                            shard_num: 0,
218                            realm_num: 0,
219                            token_num: 8798,
220                        },
221                    ),
222                    custom_fees: [
223                        CustomFee {
224                            fee_collector_account_id: Some(
225                                AccountId {
226                                    shard_num: 0,
227                                    realm_num: 0,
228                                    account: Some(
229                                        AccountNum(
230                                            4322,
231                                        ),
232                                    ),
233                                },
234                            ),
235                            all_collectors_are_exempt: false,
236                            fee: Some(
237                                FixedFee(
238                                    FixedFee {
239                                        amount: 10,
240                                        denominating_token_id: Some(
241                                            TokenId {
242                                                shard_num: 0,
243                                                realm_num: 0,
244                                                token_num: 483902,
245                                            },
246                                        ),
247                                    },
248                                ),
249                            ),
250                        },
251                        CustomFee {
252                            fee_collector_account_id: Some(
253                                AccountId {
254                                    shard_num: 0,
255                                    realm_num: 0,
256                                    account: Some(
257                                        AccountNum(
258                                            389042,
259                                        ),
260                                    ),
261                                },
262                            ),
263                            all_collectors_are_exempt: false,
264                            fee: Some(
265                                FractionalFee(
266                                    FractionalFee {
267                                        fractional_amount: Some(
268                                            Fraction {
269                                                numerator: 3,
270                                                denominator: 7,
271                                            },
272                                        ),
273                                        minimum_amount: 3,
274                                        maximum_amount: 100,
275                                        net_of_transfers: true,
276                                    },
277                                ),
278                            ),
279                        },
280                    ],
281                },
282            )
283        "#]]
284        .assert_debug_eq(&tx)
285    }
286
287    #[test]
288    fn to_from_bytes() {
289        let tx = make_transaction();
290
291        let tx2 = AnyTransaction::from_bytes(&tx.to_bytes().unwrap()).unwrap();
292
293        let tx = transaction_body(tx);
294
295        let tx2 = transaction_body(tx2);
296
297        assert_eq!(tx, tx2);
298    }
299
300    #[test]
301    fn from_proto_body() {
302        let tx = services::TokenFeeScheduleUpdateTransactionBody {
303            token_id: Some(TOKEN_ID.to_protobuf()),
304            custom_fees: custom_fees().to_vec().to_protobuf(),
305        };
306
307        let data = TokenFeeScheduleUpdateTransactionData::from_protobuf(tx).unwrap();
308
309        assert_eq!(data.token_id, Some(TOKEN_ID));
310        assert_eq!(data.custom_fees, custom_fees());
311    }
312
313    #[test]
314    fn get_set_token_id() {
315        let mut tx = TokenFeeScheduleUpdateTransaction::new();
316        tx.token_id(TOKEN_ID);
317
318        assert_eq!(tx.get_token_id(), Some(TOKEN_ID));
319    }
320
321    #[test]
322    #[should_panic]
323    fn get_set_token_id_frozen_panic() {
324        make_transaction().token_id(TOKEN_ID);
325    }
326
327    #[test]
328    fn get_set_custom_fees() {
329        let mut tx = TokenFeeScheduleUpdateTransaction::new();
330        tx.custom_fees(custom_fees());
331
332        assert_eq!(tx.get_custom_fees(), custom_fees());
333    }
334
335    #[test]
336    #[should_panic]
337    fn get_set_custom_fees_frozen_panic() {
338        make_transaction().custom_fees(custom_fees());
339    }
340}