1use crate::rpc::types::{
6 cfx::{from_primitive_access_list, receipt::Receipt, CfxAccessList},
7 Bytes, RpcAddress,
8};
9use cfx_addr::Network;
10use cfx_rpc_eth_types::Transaction as ETHTransaction;
11use cfx_types::{Space, H256, U256, U64};
12use cfxkey::Error;
13use primitives::{
14 transaction::{
15 eth_transaction::Eip155Transaction,
16 native_transaction::NativeTransaction, Action,
17 },
18 SignedTransaction, Transaction as PrimitiveTransaction, TransactionIndex,
19 TransactionWithSignature, TransactionWithSignatureSerializePart,
20};
21use serde::{Deserialize, Serialize};
22
23#[derive(Debug, Clone, PartialEq, Serialize)]
24#[serde(rename_all = "camelCase")]
25pub enum WrapTransaction {
26 NativeTransaction(Transaction),
27 EthTransaction(ETHTransaction),
28}
29
30#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct Transaction {
33 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
34 pub transaction_type: Option<U64>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub space: Option<Space>,
37 pub hash: H256,
38 pub nonce: U256,
39 pub block_hash: Option<H256>,
40 pub transaction_index: Option<U64>,
41 pub from: RpcAddress,
42 pub to: Option<RpcAddress>,
43 pub value: U256,
44 pub gas_price: U256,
45 pub gas: U256,
46 pub contract_created: Option<RpcAddress>,
47 pub data: Bytes,
48 pub storage_limit: U256,
49 pub epoch_height: U256,
50 pub chain_id: Option<U256>,
51 pub status: Option<U64>,
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub access_list: Option<CfxAccessList>,
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub max_priority_fee_per_gas: Option<U256>,
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub max_fee_per_gas: Option<U256>,
61 pub v: U256,
63 pub r: U256,
65 pub s: U256,
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub y_parity: Option<U64>,
69}
70
71pub enum PackedOrExecuted {
72 Packed(TransactionIndex),
73 Executed(Receipt),
74}
75
76impl Transaction {
77 pub fn default(network: Network) -> Result<Transaction, String> {
78 Ok(Transaction {
79 space: None,
80 hash: Default::default(),
81 nonce: Default::default(),
82 block_hash: Default::default(),
83 transaction_index: Default::default(),
84 from: RpcAddress::null(network)?,
85 to: Default::default(),
86 value: Default::default(),
87 gas_price: Default::default(),
88 gas: Default::default(),
89 contract_created: Default::default(),
90 data: Default::default(),
91 storage_limit: Default::default(),
92 epoch_height: Default::default(),
93 chain_id: Some(U256::one()),
94 status: Default::default(),
95 v: Default::default(),
96 r: Default::default(),
97 s: Default::default(),
98 access_list: Default::default(),
99 max_priority_fee_per_gas: Default::default(),
100 max_fee_per_gas: Default::default(),
101 y_parity: Default::default(),
102 transaction_type: Default::default(),
103 })
104 }
105
106 pub fn from_signed(
107 t: &SignedTransaction,
108 maybe_packed_or_executed: Option<PackedOrExecuted>, network: Network,
109 ) -> Result<Transaction, String> {
110 let mut contract_created = None;
111 let mut status: Option<U64> = None;
112 let mut block_hash = None;
113 let mut transaction_index = None;
114 match maybe_packed_or_executed {
115 None => {}
116 Some(PackedOrExecuted::Packed(tx_index)) => {
117 block_hash = Some(tx_index.block_hash);
118 transaction_index = Some(
119 tx_index.rpc_index.unwrap_or(tx_index.real_index).into(),
120 );
121 }
122 Some(PackedOrExecuted::Executed(receipt)) => {
123 block_hash = Some(receipt.block_hash);
124 transaction_index = Some(receipt.index.into());
125 if let Some(ref address) = receipt.contract_created {
126 contract_created = Some(address.clone());
127 }
128 status = Some(receipt.outcome_status);
129 }
130 }
131 let (storage_limit, epoch_height) =
132 if let PrimitiveTransaction::Native(ref tx) = t.unsigned {
133 (*tx.storage_limit(), *tx.epoch_height())
134 } else {
135 (0, 0)
136 };
137 let space = match t.space() {
138 Space::Native => None,
139 Space::Ethereum => Some(Space::Ethereum),
140 };
141 Ok(Transaction {
142 space,
143 hash: t.transaction.hash().into(),
144 nonce: t.nonce().into(),
145 block_hash,
146 transaction_index,
147 status,
148 contract_created,
149 from: RpcAddress::try_from_h160(t.sender().address, network)?,
150 to: match t.action() {
151 Action::Create => None,
152 Action::Call(ref address) => {
153 Some(RpcAddress::try_from_h160(address.clone(), network)?)
154 }
155 },
156 value: t.value().into(),
157 gas_price: t.gas_price().into(),
158 gas: t.gas().into(),
159 data: t.data().clone().into(),
160 storage_limit: storage_limit.into(),
161 epoch_height: epoch_height.into(),
162 chain_id: t.chain_id().map(|x| U256::from(x as u64)),
163 access_list: t
164 .access_list()
165 .cloned()
166 .map(|list| from_primitive_access_list(list, network)),
167 max_fee_per_gas: t.after_1559().then_some(*t.gas_price()),
168 max_priority_fee_per_gas: t
169 .after_1559()
170 .then_some(*t.max_priority_gas_price()),
171 y_parity: t.is_2718().then_some(t.transaction.v.into()),
172 transaction_type: Some(U64::from(t.type_id())),
173 v: t.transaction.v.into(),
174 r: t.transaction.r.into(),
175 s: t.transaction.s.into(),
176 })
177 }
178
179 pub fn into_signed(self) -> Result<SignedTransaction, Error> {
180 let tx_with_sig = TransactionWithSignature {
181 transaction: TransactionWithSignatureSerializePart {
182 unsigned: if self.space == Some(Space::Ethereum) {
183 Eip155Transaction {
184 nonce: self.nonce.into(),
185 gas_price: self.gas_price.into(),
186 gas: self.gas.into(),
187 action: match self.to {
188 None => Action::Create,
189 Some(address) => Action::Call(address.into()),
190 },
191 value: self.value.into(),
192 chain_id: self.chain_id.map(|x| x.as_u32()),
193 data: self.data.into(),
194 }
195 .into()
196 } else {
197 NativeTransaction {
198 nonce: self.nonce.into(),
199 gas_price: self.gas_price.into(),
200 gas: self.gas.into(),
201 action: match self.to {
202 None => Action::Create,
203 Some(address) => Action::Call(address.into()),
204 },
205 value: self.value.into(),
206 storage_limit: self.storage_limit.as_u64(),
207 epoch_height: self.epoch_height.as_u64(),
208 chain_id: self
209 .chain_id
210 .ok_or(Error::Custom(
211 "Native transaction must have chain_id".into(),
212 ))?
213 .as_u32(),
214 data: self.data.into(),
215 }
216 .into()
217 },
218 v: self.v.as_usize() as u8,
219 r: self.r.into(),
220 s: self.s.into(),
221 },
222 hash: self.hash.into(),
223 rlp_size: None,
224 };
225 let public = tx_with_sig.recover_public()?;
226 Ok(SignedTransaction::new(public, tx_with_sig))
227 }
228}