espresso_types/v0/impls/
fee_info.rs

1use std::str::FromStr;
2
3use alloy::primitives::{
4    utils::{parse_units, ParseUnits},
5    Address, U256,
6};
7use anyhow::{bail, ensure, Context};
8use ark_serialize::{
9    CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
10};
11use committable::{Commitment, Committable, RawCommitmentBuilder};
12use hotshot_contract_adapter::sol_types::Deposit;
13use hotshot_query_service::explorer::MonetaryValue;
14use hotshot_types::traits::block_contents::BuilderFee;
15use itertools::Itertools;
16use jf_merkle_tree::{
17    ForgetableMerkleTreeScheme, ForgetableUniversalMerkleTreeScheme, LookupResult,
18    MerkleCommitment, MerkleTreeError, MerkleTreeScheme, ToTraversalPath,
19    UniversalMerkleTreeScheme,
20};
21use num_traits::CheckedSub;
22use sequencer_utils::{
23    impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger,
24};
25use thiserror::Error;
26
27use crate::{
28    eth_signature_key::EthKeyPair, v0_99::IterableFeeInfo, AccountQueryData, FeeAccount,
29    FeeAccountProof, FeeAmount, FeeInfo, FeeMerkleCommitment, FeeMerkleProof, FeeMerkleTree,
30    SeqTypes,
31};
32
33/// Possible charge fee failures
34#[derive(Error, Debug, Eq, PartialEq)]
35pub enum FeeError {
36    #[error("Insuficcient Funds: have {balance:?}, required {amount:?}")]
37    InsufficientFunds {
38        balance: Option<FeeAmount>,
39        amount: FeeAmount,
40    },
41    #[error("Merkle Tree Error: {0}")]
42    MerkleTreeError(MerkleTreeError),
43}
44
45impl FeeInfo {
46    pub fn new(account: impl Into<FeeAccount>, amount: impl Into<FeeAmount>) -> Self {
47        Self {
48            account: account.into(),
49            amount: amount.into(),
50        }
51    }
52    /// The minimum fee paid by the given builder account for a proposed block.
53    // TODO this function should take the block size as an input, we need to get this information
54    // from HotShot.
55    pub fn base_fee(account: FeeAccount) -> Self {
56        Self {
57            account,
58            amount: FeeAmount::default(),
59        }
60    }
61
62    pub fn genesis() -> Self {
63        Self {
64            account: Default::default(),
65            amount: Default::default(),
66        }
67    }
68
69    pub fn account(&self) -> FeeAccount {
70        self.account
71    }
72
73    pub fn amount(&self) -> FeeAmount {
74        self.amount
75    }
76    /// Get a `Vec<FeeInfo>` from `Vec<BuilderFee>`
77    pub fn from_builder_fees(fees: Vec<BuilderFee<SeqTypes>>) -> Vec<FeeInfo> {
78        fees.into_iter().map(FeeInfo::from).collect()
79    }
80}
81
82impl IterableFeeInfo for Vec<FeeInfo> {
83    /// Get sum of fee amounts
84    fn amount(&self) -> Option<FeeAmount> {
85        self.iter()
86            // getting the u64 tests that the value fits
87            .map(|fee_info| fee_info.amount.as_u64())
88            .collect::<Option<Vec<u64>>>()
89            .and_then(|amounts| amounts.iter().try_fold(0u64, |acc, n| acc.checked_add(*n)))
90            .map(FeeAmount::from)
91    }
92
93    /// Get a `Vec` of all unique fee accounts
94    fn accounts(&self) -> Vec<FeeAccount> {
95        self.iter()
96            .unique_by(|entry| &entry.account)
97            .map(|entry| entry.account)
98            .collect()
99    }
100}
101
102impl IterableFeeInfo for Vec<BuilderFee<SeqTypes>> {
103    /// Get sum of amounts
104    fn amount(&self) -> Option<FeeAmount> {
105        self.iter()
106            .map(|fee_info| fee_info.fee_amount)
107            .try_fold(0u64, |acc, n| acc.checked_add(n))
108            .map(FeeAmount::from)
109    }
110
111    /// Get a `Vec` of all unique fee accounts
112    fn accounts(&self) -> Vec<FeeAccount> {
113        self.iter()
114            .unique_by(|entry| &entry.fee_account)
115            .map(|entry| entry.fee_account)
116            .collect()
117    }
118}
119
120impl From<BuilderFee<SeqTypes>> for FeeInfo {
121    fn from(fee: BuilderFee<SeqTypes>) -> Self {
122        Self {
123            amount: fee.fee_amount.into(),
124            account: fee.fee_account,
125        }
126    }
127}
128
129impl From<Deposit> for FeeInfo {
130    fn from(item: Deposit) -> Self {
131        Self {
132            amount: item.amount.into(),
133            account: item.user.into(),
134        }
135    }
136}
137
138impl Committable for FeeInfo {
139    fn commit(&self) -> Commitment<Self> {
140        RawCommitmentBuilder::new(&Self::tag())
141            .fixed_size_field("account", &self.account.to_fixed_bytes())
142            .fixed_size_field("amount", &self.amount.to_fixed_bytes())
143            .finalize()
144    }
145    fn tag() -> String {
146        "FEE_INFO".into()
147    }
148}
149
150impl_serde_from_string_or_integer!(FeeAmount);
151impl_to_fixed_bytes!(FeeAmount, U256);
152
153impl From<u64> for FeeAmount {
154    fn from(amt: u64) -> Self {
155        Self(U256::from(amt))
156    }
157}
158
159impl From<FeeAmount> for MonetaryValue {
160    fn from(value: FeeAmount) -> Self {
161        MonetaryValue::eth(value.0.to::<u128>() as i128)
162    }
163}
164
165impl CheckedSub for FeeAmount {
166    fn checked_sub(&self, v: &Self) -> Option<Self> {
167        self.0.checked_sub(v.0).map(FeeAmount)
168    }
169}
170
171impl FromStr for FeeAmount {
172    type Err = <U256 as FromStr>::Err;
173
174    fn from_str(s: &str) -> Result<Self, Self::Err> {
175        Ok(Self(s.parse()?))
176    }
177}
178
179impl FromStringOrInteger for FeeAmount {
180    type Binary = ethers_core::types::U256;
181    type Integer = u64;
182
183    fn from_binary(b: Self::Binary) -> anyhow::Result<Self> {
184        Ok(Self(U256::from_limbs(b.0)))
185    }
186
187    fn from_integer(i: Self::Integer) -> anyhow::Result<Self> {
188        Ok(i.into())
189    }
190
191    fn from_string(s: String) -> anyhow::Result<Self> {
192        // For backwards compatibility, we have an ad hoc parser for WEI amounts represented as hex
193        // strings.
194        if let Some(s) = s.strip_prefix("0x") {
195            return Ok(Self(U256::from_str_radix(s, 16)?));
196        }
197
198        // Strip an optional non-numeric suffix, which will be interpreted as a unit.
199        let (base, unit) = s
200            .split_once(char::is_whitespace)
201            .unwrap_or((s.as_str(), "wei"));
202        match parse_units(base, unit)? {
203            ParseUnits::U256(n) => Ok(Self(n)),
204            ParseUnits::I256(_) => bail!("amount cannot be negative"),
205        }
206    }
207
208    fn to_binary(&self) -> anyhow::Result<Self::Binary> {
209        Ok(ethers_core::types::U256(self.0.into_limbs()))
210    }
211
212    fn to_string(&self) -> anyhow::Result<String> {
213        Ok(format!("{self}"))
214    }
215}
216
217impl FeeAmount {
218    pub fn as_u64(&self) -> Option<u64> {
219        if self.0 <= U256::from(u64::MAX) {
220            Some(self.0.to::<u64>())
221        } else {
222            None
223        }
224    }
225}
226impl FeeAccount {
227    /// Return inner `Address`
228    pub fn address(&self) -> Address {
229        self.0
230    }
231    /// Return byte slice representation of inner `Address` type
232    pub fn as_bytes(&self) -> &[u8] {
233        self.0.as_slice()
234    }
235    /// Return array containing underlying bytes of inner `Address` type
236    pub fn to_fixed_bytes(self) -> [u8; 20] {
237        self.0.into_array()
238    }
239    pub fn test_key_pair() -> EthKeyPair {
240        EthKeyPair::from_mnemonic(
241            "test test test test test test test test test test test junk",
242            0u32,
243        )
244        .unwrap()
245    }
246}
247
248impl FromStr for FeeAccount {
249    type Err = anyhow::Error;
250
251    fn from_str(s: &str) -> Result<Self, Self::Err> {
252        Ok(Self(s.parse()?))
253    }
254}
255
256impl Valid for FeeAmount {
257    fn check(&self) -> Result<(), SerializationError> {
258        Ok(())
259    }
260}
261
262impl Valid for FeeAccount {
263    fn check(&self) -> Result<(), SerializationError> {
264        Ok(())
265    }
266}
267
268impl CanonicalSerialize for FeeAmount {
269    fn serialize_with_mode<W: std::io::prelude::Write>(
270        &self,
271        mut writer: W,
272        _compress: Compress,
273    ) -> Result<(), SerializationError> {
274        Ok(writer.write_all(&self.to_fixed_bytes())?)
275    }
276
277    fn serialized_size(&self, _compress: Compress) -> usize {
278        core::mem::size_of::<U256>()
279    }
280}
281impl CanonicalDeserialize for FeeAmount {
282    fn deserialize_with_mode<R: Read>(
283        mut reader: R,
284        _compress: Compress,
285        _validate: Validate,
286    ) -> Result<Self, SerializationError> {
287        let mut bytes = [0u8; core::mem::size_of::<U256>()];
288        reader.read_exact(&mut bytes)?;
289        let value = U256::from_le_slice(&bytes);
290        Ok(Self(value))
291    }
292}
293impl CanonicalSerialize for FeeAccount {
294    fn serialize_with_mode<W: std::io::prelude::Write>(
295        &self,
296        mut writer: W,
297        _compress: Compress,
298    ) -> Result<(), SerializationError> {
299        Ok(writer.write_all(self.0.as_slice())?)
300    }
301
302    fn serialized_size(&self, _compress: Compress) -> usize {
303        core::mem::size_of::<Address>()
304    }
305}
306impl CanonicalDeserialize for FeeAccount {
307    fn deserialize_with_mode<R: Read>(
308        mut reader: R,
309        _compress: Compress,
310        _validate: Validate,
311    ) -> Result<Self, SerializationError> {
312        let mut bytes = [0u8; core::mem::size_of::<Address>()];
313        reader.read_exact(&mut bytes)?;
314        let value = Address::from_slice(&bytes);
315        Ok(Self(value))
316    }
317}
318
319impl ToTraversalPath<256> for FeeAccount {
320    fn to_traversal_path(&self, height: usize) -> Vec<usize> {
321        self.0
322            .as_slice()
323            .iter()
324            .take(height)
325            .map(|i| *i as usize)
326            .collect()
327    }
328}
329
330#[allow(dead_code)]
331impl FeeAccountProof {
332    pub fn presence(
333        pos: FeeAccount,
334        proof: <FeeMerkleTree as MerkleTreeScheme>::MembershipProof,
335    ) -> Self {
336        Self {
337            account: pos.into(),
338            proof: FeeMerkleProof::Presence(proof),
339        }
340    }
341
342    pub fn absence(
343        pos: FeeAccount,
344        proof: <FeeMerkleTree as UniversalMerkleTreeScheme>::NonMembershipProof,
345    ) -> Self {
346        Self {
347            account: pos.into(),
348            proof: FeeMerkleProof::Absence(proof),
349        }
350    }
351
352    pub fn prove(tree: &FeeMerkleTree, account: Address) -> Option<(Self, U256)> {
353        match tree.universal_lookup(FeeAccount(account)) {
354            LookupResult::Ok(balance, proof) => Some((
355                Self {
356                    account,
357                    proof: FeeMerkleProof::Presence(proof),
358                },
359                balance.0,
360            )),
361            LookupResult::NotFound(proof) => Some((
362                Self {
363                    account,
364                    proof: FeeMerkleProof::Absence(proof),
365                },
366                U256::ZERO,
367            )),
368            LookupResult::NotInMemory => None,
369        }
370    }
371
372    pub fn verify(&self, comm: &FeeMerkleCommitment) -> anyhow::Result<U256> {
373        match &self.proof {
374            FeeMerkleProof::Presence(proof) => {
375                ensure!(
376                    FeeMerkleTree::verify(comm.digest(), FeeAccount(self.account), proof)?.is_ok(),
377                    "invalid proof"
378                );
379                Ok(proof
380                    .elem()
381                    .context("presence proof is missing account balance")?
382                    .0)
383            },
384            FeeMerkleProof::Absence(proof) => {
385                let tree = FeeMerkleTree::from_commitment(comm);
386                ensure!(
387                    tree.non_membership_verify(FeeAccount(self.account), proof)?,
388                    "invalid proof"
389                );
390                Ok(U256::ZERO)
391            },
392        }
393    }
394
395    pub fn remember(&self, tree: &mut FeeMerkleTree) -> anyhow::Result<()> {
396        match &self.proof {
397            FeeMerkleProof::Presence(proof) => {
398                tree.remember(
399                    FeeAccount(self.account),
400                    proof
401                        .elem()
402                        .context("presence proof is missing account balance")?,
403                    proof,
404                )?;
405                Ok(())
406            },
407            FeeMerkleProof::Absence(proof) => {
408                tree.non_membership_remember(FeeAccount(self.account), proof)?;
409                Ok(())
410            },
411        }
412    }
413}
414
415impl From<(FeeAccountProof, U256)> for AccountQueryData {
416    fn from((proof, balance): (FeeAccountProof, U256)) -> Self {
417        Self { balance, proof }
418    }
419}
420
421/// Get a partial snapshot of the given fee state, which contains only the specified accounts.
422///
423/// Fails if one of the requested accounts is not represented in the original `state`.
424pub fn retain_accounts(
425    state: &FeeMerkleTree,
426    accounts: impl IntoIterator<Item = FeeAccount>,
427) -> anyhow::Result<FeeMerkleTree> {
428    let mut snapshot = FeeMerkleTree::from_commitment(state.commitment());
429    for account in accounts {
430        match state.universal_lookup(account) {
431            LookupResult::Ok(elem, proof) => {
432                // This remember cannot fail, since we just constructed a valid proof, and are
433                // remembering into a tree with the same commitment.
434                snapshot.remember(account, *elem, proof).unwrap();
435            },
436            LookupResult::NotFound(proof) => {
437                // Likewise this cannot fail.
438                snapshot.non_membership_remember(account, proof).unwrap()
439            },
440            LookupResult::NotInMemory => {
441                bail!("missing account {account}");
442            },
443        }
444    }
445
446    Ok(snapshot)
447}
448
449#[cfg(test)]
450mod test {
451    use super::{Address, IterableFeeInfo};
452    use crate::{FeeAccount, FeeAmount, FeeInfo};
453
454    #[test]
455    fn test_iterable_fee_info() {
456        let addr = Address::default();
457        let fee = FeeInfo::new(addr, FeeAmount::from(1));
458        let fees = vec![fee, fee, fee];
459        // check the sum of amounts
460        let sum = fees.amount().unwrap();
461        assert_eq!(FeeAmount::from(3), sum);
462
463        // check accounts collector
464        let accounts = fees.accounts();
465        assert_eq!(vec![FeeAccount::from(Address::default())], accounts);
466    }
467}