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 gas = self.gas.ok_or("should have gas")?;
139 let nonce = self.nonce.ok_or("should have nonce")?;
140 let transaction_type = self.transaction_type();
141 let action = self.to.map_or(Action::Create, |rpc_addr| {
142 Action::Call(rpc_addr.hex_address)
143 });
144
145 let value = self.value.unwrap_or_default();
146 let storage_limit = self
147 .storage_limit
148 .map(|v| v.as_u64())
149 .ok_or("should have storage_limit")?;
150 let data = self.data.unwrap_or_default().into_vec();
151
152 let access_list = self.access_list.unwrap_or(vec![]);
153
154 let typed_native_tx = match transaction_type {
155 LEGACY_TX_TYPE => {
156 let gas_price =
157 self.gas_price.ok_or("should have gas_price")?;
158 Cip155(NativeTransaction {
159 nonce,
160 action,
161 gas,
162 gas_price,
163 value,
164 storage_limit,
165 epoch_height,
166 chain_id,
167 data,
168 })
169 }
170 CIP2930_TYPE => {
171 let gas_price =
172 self.gas_price.ok_or("should have gas_price")?;
173 Cip2930(Cip2930Transaction {
174 nonce,
175 gas_price,
176 gas,
177 action,
178 value,
179 storage_limit,
180 epoch_height,
181 chain_id,
182 data,
183 access_list: to_primitive_access_list(access_list),
184 })
185 }
186 CIP1559_TYPE => {
187 let max_fee_per_gas = self
188 .max_fee_per_gas
189 .ok_or("should have max_fee_per_gas")?;
190 let max_priority_fee_per_gas = self
191 .max_priority_fee_per_gas
192 .ok_or("should have max_priority_fee_per_gas")?;
193 Cip1559(Cip1559Transaction {
194 nonce,
195 action,
196 gas,
197 value,
198 max_fee_per_gas,
199 max_priority_fee_per_gas,
200 storage_limit,
201 epoch_height,
202 chain_id,
203 data,
204 access_list: to_primitive_access_list(access_list),
205 })
206 }
207 x => {
209 return Err(format!("Unrecognized transaction type: {:?}", x));
210 }
211 };
212
213 let tx = Transaction::Native(typed_native_tx);
214 let password = password.map(Password::from);
215 let sig = accounts
216 .sign(
217 self.from.unwrap().into(),
218 password,
219 tx.hash_for_compute_signature(),
220 )
221 .map_err(|e| format!("failed to sign transaction: {:?}", e))?;
223
224 Ok(tx.with_signature(sig))
225 }
226
227 pub fn sign_call(
228 self, epoch_height: u64, chain_id: u32, max_gas: Option<U256>,
229 ) -> Result<SignedTransaction, ErrorObjectOwned> {
230 let max_gas = max_gas.unwrap_or(DEFAULT_CFX_GAS_CALL_REQUEST.into());
231 let gas = self.gas.unwrap_or(max_gas);
232 if gas > max_gas {
233 bail!(invalid_params(
234 "gas",
235 Some(format!(
236 "specified gas is larger than max gas {:?}",
237 max_gas
238 ))
239 ))
240 }
241 let transaction_type = self.transaction_type();
242 let nonce = self.nonce.unwrap_or_default();
243 let action = self.to.map_or(Action::Create, |rpc_addr| {
244 Action::Call(rpc_addr.hex_address)
245 });
246
247 let value = self.value.unwrap_or_default();
248 let storage_limit = self
249 .storage_limit
250 .map(|v| v.as_u64())
251 .unwrap_or(std::u64::MAX);
252 let data = self.data.unwrap_or_default().into_vec();
253
254 let gas_price = self.gas_price.unwrap_or(1.into());
255 let max_fee_per_gas = self
256 .max_fee_per_gas
257 .or(self.max_priority_fee_per_gas)
258 .unwrap_or(gas_price);
259 let max_priority_fee_per_gas =
260 self.max_priority_fee_per_gas.unwrap_or(U256::zero());
261 let access_list = self.access_list.unwrap_or(vec![]);
262
263 let transaction = match transaction_type {
264 LEGACY_TX_TYPE => Cip155(NativeTransaction {
265 nonce,
266 action,
267 gas,
268 gas_price,
269 value,
270 storage_limit,
271 epoch_height,
272 chain_id,
273 data,
274 }),
275 CIP2930_TYPE => Cip2930(Cip2930Transaction {
276 nonce,
277 gas_price,
278 gas,
279 action,
280 value,
281 storage_limit,
282 epoch_height,
283 chain_id,
284 data,
285 access_list: to_primitive_access_list(access_list),
286 }),
287 CIP1559_TYPE => Cip1559(Cip1559Transaction {
288 nonce,
289 action,
290 gas,
291 value,
292 max_fee_per_gas,
293 max_priority_fee_per_gas,
294 storage_limit,
295 epoch_height,
296 chain_id,
297 data,
298 access_list: to_primitive_access_list(access_list),
299 }),
300 x => {
302 return Err(invalid_params_msg(&format!(
303 "Unrecognized transaction type: {}",
304 x
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::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}