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