client/rpc/types/cfx/
transaction_request.rs

1// Copyright 2019 Conflux Foundation. All rights reserved.
2// Conflux is free software and distributed under GNU General Public License.
3// See http://www.gnu.org/licenses/
4
5use crate::rpc::{
6    errors::{invalid_params, invalid_params_check},
7    types::{
8        address::RpcAddress,
9        cfx::{
10            check_rpc_address_network, check_two_rpc_address_network_match,
11            to_primitive_access_list, CfxAccessList,
12        },
13        Bytes,
14    },
15    CoreResult,
16};
17use cfx_addr::Network;
18use cfx_parameters::{
19    block::{
20        CIP1559_CORE_TRANSACTION_GAS_RATIO, DEFAULT_TARGET_BLOCK_GAS_LIMIT,
21    },
22    RATIO_BASE_TEN,
23};
24use cfx_types::{Address, AddressSpaceUtil, U256, U64};
25use cfx_util_macros::bail;
26use cfxcore_accounts::AccountProvider;
27use cfxkey::Password;
28use primitives::{
29    transaction::{
30        Action, Cip1559Transaction, Cip2930Transaction, NativeTransaction,
31        TypedNativeTransaction::*, CIP1559_TYPE, CIP2930_TYPE, LEGACY_TX_TYPE,
32    },
33    SignedTransaction, Transaction, TransactionWithSignature,
34};
35use serde::{Deserialize, Serialize};
36use std::{convert::Into, sync::Arc};
37
38/// The maximum gas limit accepted by most tx pools.
39pub const DEFAULT_CFX_GAS_CALL_REQUEST: u64 = DEFAULT_TARGET_BLOCK_GAS_LIMIT
40    * CIP1559_CORE_TRANSACTION_GAS_RATIO
41    / RATIO_BASE_TEN;
42
43#[derive(Debug, Default, Deserialize, PartialEq, Serialize, Clone)]
44#[serde(rename_all = "camelCase")]
45pub struct TransactionRequest {
46    /// From
47    pub from: Option<RpcAddress>,
48    /// To
49    pub to: Option<RpcAddress>,
50    /// Gas Price
51    pub gas_price: Option<U256>,
52    /// Gas
53    pub gas: Option<U256>,
54    /// Value
55    pub value: Option<U256>,
56    /// Data
57    pub data: Option<Bytes>,
58    /// Nonce
59    pub nonce: Option<U256>,
60    /// StorageLimit
61    pub storage_limit: Option<U64>,
62    /// Access list in EIP-2930
63    pub access_list: Option<CfxAccessList>,
64    pub max_fee_per_gas: Option<U256>,
65    pub max_priority_fee_per_gas: Option<U256>,
66    #[serde(rename = "type")]
67    pub transaction_type: Option<U64>,
68    ///
69    pub chain_id: Option<U256>,
70    ///
71    pub epoch_height: Option<U256>,
72}
73
74#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
75#[serde(rename_all = "camelCase")]
76pub struct EstimateGasAndCollateralResponse {
77    /// The recommended gas_limit.
78    pub gas_limit: U256,
79    /// The amount of gas used in the execution.
80    pub gas_used: U256,
81    /// The number of bytes collateralized in the execution.
82    pub storage_collateralized: U64,
83}
84
85#[derive(Debug, Default, PartialEq, Deserialize, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct CheckBalanceAgainstTransactionResponse {
88    /// Whether the account should pay transaction fee by self.
89    pub will_pay_tx_fee: bool,
90    /// Whether the account should pay collateral by self.
91    pub will_pay_collateral: bool,
92    /// Whether the account balance is enough for this transaction.
93    pub is_balance_enough: bool,
94}
95
96impl TransactionRequest {
97    pub fn check_rpc_address_network(
98        &self, param_name: &str, expected: &Network,
99    ) -> CoreResult<()> {
100        let rpc_request_network = invalid_params_check(
101            param_name,
102            check_two_rpc_address_network_match(
103                self.from.as_ref(),
104                self.to.as_ref(),
105            ),
106        )?;
107        invalid_params_check(
108            param_name,
109            check_rpc_address_network(rpc_request_network, expected),
110        )
111        .map_err(|e| e.into())
112    }
113
114    pub fn transaction_type(&self) -> u8 {
115        if let Some(tx_type) = self.transaction_type {
116            tx_type.as_usize() as u8
117        } else {
118            if self.max_fee_per_gas.is_some()
119                || self.max_priority_fee_per_gas.is_some()
120            {
121                CIP1559_TYPE
122            } else if self.access_list.is_some() {
123                CIP2930_TYPE
124            } else {
125                LEGACY_TX_TYPE
126            }
127        }
128    }
129
130    pub fn has_gas_price(&self) -> bool {
131        self.gas_price.is_some()
132            || self.max_fee_per_gas.is_some()
133            || self.max_priority_fee_per_gas.is_some()
134    }
135
136    pub fn sign_with(
137        self, epoch_height: u64, chain_id: u32, password: Option<String>,
138        accounts: Arc<AccountProvider>,
139    ) -> CoreResult<TransactionWithSignature> {
140        let gas = self.gas.ok_or("should have gas")?;
141        let nonce = self.nonce.ok_or("should have nonce")?;
142        let transaction_type = self.transaction_type();
143        let action = self.to.map_or(Action::Create, |rpc_addr| {
144            Action::Call(rpc_addr.hex_address)
145        });
146
147        let value = self.value.unwrap_or_default();
148        let storage_limit = self
149            .storage_limit
150            .map(|v| v.as_u64())
151            .ok_or("should have storage_limit")?;
152        let data = self.data.unwrap_or_default().into_vec();
153
154        let access_list = self.access_list.unwrap_or(vec![]);
155
156        let typed_native_tx = match transaction_type {
157            LEGACY_TX_TYPE => {
158                let gas_price =
159                    self.gas_price.ok_or("should have gas_price")?;
160                Cip155(NativeTransaction {
161                    nonce,
162                    action,
163                    gas,
164                    gas_price,
165                    value,
166                    storage_limit,
167                    epoch_height,
168                    chain_id,
169                    data,
170                })
171            }
172            CIP2930_TYPE => {
173                let gas_price =
174                    self.gas_price.ok_or("should have gas_price")?;
175                Cip2930(Cip2930Transaction {
176                    nonce,
177                    gas_price,
178                    gas,
179                    action,
180                    value,
181                    storage_limit,
182                    epoch_height,
183                    chain_id,
184                    data,
185                    access_list: to_primitive_access_list(access_list),
186                })
187            }
188            CIP1559_TYPE => {
189                let max_fee_per_gas = self
190                    .max_fee_per_gas
191                    .ok_or("should have max_fee_per_gas")?;
192                let max_priority_fee_per_gas = self
193                    .max_priority_fee_per_gas
194                    .ok_or("should have max_priority_fee_per_gas")?;
195                Cip1559(Cip1559Transaction {
196                    nonce,
197                    action,
198                    gas,
199                    value,
200                    max_fee_per_gas,
201                    max_priority_fee_per_gas,
202                    storage_limit,
203                    epoch_height,
204                    chain_id,
205                    data,
206                    access_list: to_primitive_access_list(access_list),
207                })
208            }
209            // TODO(7702): support transaction 7702
210            x => {
211                return Err(
212                    invalid_params("Unrecognized transaction type", x).into()
213                );
214            }
215        };
216
217        let tx = Transaction::Native(typed_native_tx);
218        let password = password.map(Password::from);
219        let sig = accounts
220            .sign(
221                self.from.unwrap().into(),
222                password,
223                tx.hash_for_compute_signature(),
224            )
225            // TODO: sign error into secret store error codes.
226            .map_err(|e| format!("failed to sign transaction: {:?}", e))?;
227
228        Ok(tx.with_signature(sig))
229    }
230
231    pub fn sign_call(
232        self, epoch_height: u64, chain_id: u32, max_gas: Option<U256>,
233    ) -> CoreResult<SignedTransaction> {
234        let max_gas = max_gas.unwrap_or(DEFAULT_CFX_GAS_CALL_REQUEST.into());
235        let gas = self.gas.unwrap_or(max_gas);
236        if gas > max_gas {
237            bail!(invalid_params(
238                "gas",
239                format!("specified gas is larger than max gas {:?}", max_gas)
240            ))
241        }
242        let transaction_type = self.transaction_type();
243        let nonce = self.nonce.unwrap_or_default();
244        let action = self.to.map_or(Action::Create, |rpc_addr| {
245            Action::Call(rpc_addr.hex_address)
246        });
247
248        let value = self.value.unwrap_or_default();
249        let storage_limit = self
250            .storage_limit
251            .map(|v| v.as_u64())
252            .unwrap_or(std::u64::MAX);
253        let data = self.data.unwrap_or_default().into_vec();
254
255        let gas_price = self.gas_price.unwrap_or(1.into());
256        let max_fee_per_gas = self
257            .max_fee_per_gas
258            .or(self.max_priority_fee_per_gas)
259            .unwrap_or(gas_price);
260        let max_priority_fee_per_gas =
261            self.max_priority_fee_per_gas.unwrap_or(U256::zero());
262        let access_list = self.access_list.unwrap_or(vec![]);
263
264        let transaction = match transaction_type {
265            LEGACY_TX_TYPE => Cip155(NativeTransaction {
266                nonce,
267                action,
268                gas,
269                gas_price,
270                value,
271                storage_limit,
272                epoch_height,
273                chain_id,
274                data,
275            }),
276            CIP2930_TYPE => Cip2930(Cip2930Transaction {
277                nonce,
278                gas_price,
279                gas,
280                action,
281                value,
282                storage_limit,
283                epoch_height,
284                chain_id,
285                data,
286                access_list: to_primitive_access_list(access_list),
287            }),
288            CIP1559_TYPE => Cip1559(Cip1559Transaction {
289                nonce,
290                action,
291                gas,
292                value,
293                max_fee_per_gas,
294                max_priority_fee_per_gas,
295                storage_limit,
296                epoch_height,
297                chain_id,
298                data,
299                access_list: to_primitive_access_list(access_list),
300            }),
301            // TODO(7702): support 7702 transaction
302            x => {
303                return Err(
304                    invalid_params("Unrecognized transaction type", x).into()
305                );
306            }
307        };
308
309        let from = self
310            .from
311            .map_or_else(|| Address::zero(), |rpc_addr| rpc_addr.hex_address);
312
313        Ok(transaction.fake_sign_rpc(from.with_native_space()))
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::TransactionRequest;
320
321    use crate::rpc::types::address::RpcAddress;
322    use cfx_addr::Network;
323    use cfx_types::{H160, U256, U64};
324    use rustc_hex::FromHex;
325    use serde_json;
326    use std::str::FromStr;
327
328    #[test]
329    fn call_request_deserialize() {
330        let expected = TransactionRequest {
331            from: Some(
332                RpcAddress::try_from_h160(
333                    H160::from_low_u64_be(1),
334                    Network::Main,
335                )
336                .unwrap(),
337            ),
338            to: Some(
339                RpcAddress::try_from_h160(
340                    H160::from_low_u64_be(2),
341                    Network::Main,
342                )
343                .unwrap(),
344            ),
345            gas_price: Some(U256::from(1)),
346            gas: Some(U256::from(2)),
347            value: Some(U256::from(3)),
348            data: Some(vec![0x12, 0x34, 0x56].into()),
349            storage_limit: Some(U64::from_str("7b").unwrap()),
350            nonce: Some(U256::from(4)),
351            ..Default::default()
352        };
353
354        let s = r#"{
355            "from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6",
356            "to":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJD0WN6U9U",
357            "gasPrice":"0x1",
358            "gas":"0x2",
359            "value":"0x3",
360            "data":"0x123456",
361            "storageLimit":"0x7b",
362            "nonce":"0x4"
363        }"#;
364        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
365        assert!(
366            deserialized_result.is_ok(),
367            "serialized str should look like {}",
368            serde_json::to_string(&expected).unwrap()
369        );
370        assert_eq!(deserialized_result.unwrap(), expected);
371    }
372
373    #[test]
374    fn call_request_deserialize2() {
375        let expected = TransactionRequest {
376            from: Some(RpcAddress::try_from_h160(H160::from_str("160e8dd61c5d32be8058bb8eb970870f07233155").unwrap(),  Network::Main ).unwrap()),
377            to: Some(RpcAddress::try_from_h160(H160::from_str("846e8dd67c5d32be8058bb8eb970870f07244567").unwrap(), Network::Main).unwrap()),
378            gas_price: Some(U256::from_str("9184e72a000").unwrap()),
379            gas: Some(U256::from_str("76c0").unwrap()),
380            value: Some(U256::from_str("9184e72a").unwrap()),
381            storage_limit: Some(U64::from_str("3344adf").unwrap()),
382            data: Some("d46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675".from_hex::<Vec<u8>>().unwrap().into()),
383            nonce: None,
384            ..Default::default()
385        };
386
387        let s = r#"{
388            "from": "CFX:TYPE.USER:AANA7DS0DVSXFTYANC727SNUU6HUSJ3VMYC3F1AY93",
389            "to": "CFX:TYPE.CONTRACT:ACCG7DS0TVSXFTYANC727SNUU6HUSKCFP6KB3NFJ02",
390            "gas": "0x76c0",
391            "gasPrice": "0x9184e72a000",
392            "value": "0x9184e72a",
393            "storageLimit":"0x3344adf",
394            "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
395        }"#;
396        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
397        assert!(
398            deserialized_result.is_ok(),
399            "serialized str should look like {}",
400            serde_json::to_string(&expected).unwrap()
401        );
402        assert_eq!(deserialized_result.unwrap(), expected);
403    }
404
405    #[test]
406    fn call_request_deserialize_empty() {
407        let expected = TransactionRequest {
408            from: Some(
409                RpcAddress::try_from_h160(
410                    H160::from_low_u64_be(1),
411                    Network::Main,
412                )
413                .unwrap(),
414            ),
415            to: None,
416            gas_price: None,
417            gas: None,
418            value: None,
419            data: None,
420            storage_limit: None,
421            nonce: None,
422            ..Default::default()
423        };
424
425        let s = r#"{"from":"CFX:TYPE.BUILTIN:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEJC4EYEY6"}"#;
426        let deserialized_result = serde_json::from_str::<TransactionRequest>(s);
427        assert!(
428            deserialized_result.is_ok(),
429            "serialized str should look like {}",
430            serde_json::to_string(&expected).unwrap()
431        );
432        assert_eq!(deserialized_result.unwrap(), expected);
433    }
434}