df_hyperliquid_core/client/evm/
tx.rs1#[cfg(feature = "sign")]
14use crate::account::{LocalKey, recover};
15use crate::account::{SignError, Signature};
16use crate::client::evm::rlp;
17use crate::client::evm::u256::{B256, U256};
18use crate::utils::Address;
19use crate::utils::sign::keccak256;
20use serde::Deserialize;
21
22pub const EIP1559: u8 = 0x02;
24
25pub const GAS_HEADROOM: u64 = 130;
28
29const BASE_FEE_TIMES: u64 = 2;
31
32#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct AccessItem {
36 pub address: Address,
37 pub storage_keys: Vec<B256>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Default)]
44pub struct Eip1559 {
45 pub chain_id: u64,
46 pub nonce: u64,
47 pub max_priority_fee: U256,
48 pub max_fee: U256,
49 pub gas: u64,
50 pub to: Option<Address>,
52 pub value: U256,
53 pub data: Vec<u8>,
54 pub access_list: Vec<AccessItem>,
55}
56
57impl Eip1559 {
58 fn fields(&self) -> Vec<Vec<u8>> {
59 let to = match &self.to {
60 Some(to) => rlp::bytes(to.bytes()),
61 None => rlp::bytes(&[]),
62 };
63 let access: Vec<Vec<u8>> = self
64 .access_list
65 .iter()
66 .map(|item| {
67 let keys: Vec<Vec<u8>> = item.storage_keys.iter().map(|key| rlp::bytes(&key.0)).collect();
68 rlp::list(&[rlp::bytes(item.address.bytes()), rlp::list(&keys)])
69 })
70 .collect();
71 vec![
72 rlp::uint(u128::from(self.chain_id)),
73 rlp::uint(u128::from(self.nonce)),
74 rlp::u256(self.max_priority_fee),
75 rlp::u256(self.max_fee),
76 rlp::uint(u128::from(self.gas)),
77 to,
78 rlp::u256(self.value),
79 rlp::bytes(&self.data),
80 rlp::list(&access),
81 ]
82 }
83
84 #[must_use]
86 pub fn unsigned(&self) -> Vec<u8> {
87 typed(&self.fields())
88 }
89
90 #[must_use]
91 pub fn signing_hash(&self) -> B256 {
92 B256(keccak256(&self.unsigned()))
93 }
94
95 pub fn signed(&self, signature: &Signature) -> Result<Vec<u8>, SignError> {
99 let parity = match signature.recovery_id() {
100 Some(parity @ (0 | 1)) => parity,
101 _ => return Err(SignError::BadV(signature.v())),
102 };
103 let mut fields = self.fields();
104 fields.push(rlp::uint(u128::from(parity)));
105 fields.push(rlp::word(signature.r()));
106 fields.push(rlp::word(signature.s()));
107 Ok(typed(&fields))
108 }
109
110 #[cfg(feature = "sign")]
112 pub fn sign(&self, key: &LocalKey) -> Result<Vec<u8>, SignError> {
113 self.signed(&key.sign_digest(&self.signing_hash().0)?)
114 }
115
116 #[cfg(feature = "sign")]
118 pub fn signer(&self, signature: &Signature) -> Result<Address, SignError> {
119 recover(&self.signing_hash().0, signature)
120 }
121
122 #[must_use]
124 pub fn created(&self, from: Address) -> Option<Address> {
125 self.to.is_none().then(|| create_address(from, self.nonce))
126 }
127
128 #[must_use]
130 pub fn gas_ceiling(&self) -> U256 {
131 self.max_fee.saturating_mul(U256::from_u64(self.gas))
132 }
133
134 #[must_use]
136 pub fn most_spent(&self) -> Option<U256> {
137 self.max_fee.checked_mul(U256::from_u64(self.gas))?.checked_add(self.value)
138 }
139}
140
141fn typed(fields: &[Vec<u8>]) -> Vec<u8> {
142 let mut out = vec![EIP1559];
143 out.extend(rlp::list(fields));
144 out
145}
146
147#[must_use]
149pub fn tx_hash(raw: &[u8]) -> B256 {
150 B256(keccak256(raw))
151}
152
153#[must_use]
156pub fn create_address(from: Address, nonce: u64) -> Address {
157 let hash = keccak256(&rlp::list(&[rlp::bytes(from.bytes()), rlp::uint(u128::from(nonce))]));
158 let mut bytes = [0u8; 20];
159 bytes.copy_from_slice(&hash[12..]);
160 Address::from_bytes(bytes)
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Default)]
165pub struct Call {
166 pub to: Option<Address>,
168 pub value: U256,
169 pub data: Vec<u8>,
170}
171
172impl Call {
173 #[must_use]
175 pub fn transaction(self, chain_id: u64, nonce: u64, gas: u64, fees: Fees) -> Eip1559 {
176 Eip1559 {
177 chain_id,
178 nonce,
179 max_priority_fee: fees.max_priority_fee,
180 max_fee: fees.max_fee,
181 gas,
182 to: self.to,
183 value: self.value,
184 data: self.data,
185 access_list: Vec::new(),
186 }
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
192pub struct Fees {
193 pub max_priority_fee: U256,
194 pub max_fee: U256,
195}
196
197impl Fees {
198 #[must_use]
201 pub fn over_base(base: U256, tip: U256) -> Fees {
202 let max_fee = base.saturating_mul(U256::from_u64(BASE_FEE_TIMES)).saturating_add(tip);
203 Fees { max_priority_fee: tip, max_fee }
204 }
205}
206
207#[must_use]
209pub fn with_headroom(gas: u64) -> u64 {
210 let wide = u128::from(gas) * u128::from(GAS_HEADROOM) / 100;
211 u64::try_from(wide).unwrap_or(u64::MAX)
212}
213
214#[cfg(test)]
215#[path = "tests/tx.rs"]
216mod tests;