sequencer_utils/
lib.rs

1use std::time::Duration;
2
3use alloy::{
4    contract::SolCallBuilder,
5    network::ReceiptResponse,
6    primitives::U256,
7    providers::{Provider, ProviderBuilder},
8    rpc::types::TransactionReceipt,
9    sol_types::{GenericContractError, SolCall},
10};
11use anyhow::anyhow;
12use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError};
13use committable::{Commitment, Committable};
14use tokio::time::sleep;
15use url::Url;
16
17pub mod build_info;
18pub mod logging;
19pub mod ser;
20pub mod test_utils;
21
22pub async fn wait_for_http(
23    url: &Url,
24    interval: Duration,
25    max_retries: usize,
26) -> Result<usize, String> {
27    for i in 0..(max_retries + 1) {
28        let res = surf::get(url).await;
29        if res.is_ok() {
30            tracing::debug!("Connected to {url}");
31            return Ok(i);
32        }
33        tracing::debug!("Waiting for {url}, retrying in {interval:?}");
34        sleep(interval).await;
35    }
36    Err(format!("Url {url:?} not available."))
37}
38
39pub async fn wait_for_rpc(
40    url: &Url,
41    interval: Duration,
42    max_retries: usize,
43) -> Result<usize, String> {
44    let retries = wait_for_http(url, interval, max_retries).await?;
45    let client = ProviderBuilder::new().connect_http(url.clone());
46    for i in retries..(max_retries + 1) {
47        if client.get_block_number().await.is_ok() {
48            tracing::debug!("JSON-RPC ready at {url}");
49            return Ok(i);
50        }
51        tracing::debug!("Waiting for JSON-RPC at {url}, retrying in {interval:?}");
52        sleep(interval).await;
53    }
54
55    Err(format!("No JSON-RPC at {url}"))
56}
57
58/// converting a keccak256-based structured commitment (32 bytes) into type `U256`
59pub fn commitment_to_u256<T: Committable>(comm: Commitment<T>) -> U256 {
60    let mut buf = vec![];
61    comm.serialize_uncompressed(&mut buf).unwrap();
62    U256::from_le_slice(&buf)
63}
64
65/// converting a `U256` value into a keccak256-based structured commitment (32 bytes)
66pub fn u256_to_commitment<T: Committable>(comm: U256) -> Result<Commitment<T>, SerializationError> {
67    Commitment::deserialize_uncompressed_unchecked(&*comm.to_le_bytes_vec())
68}
69
70/// Implement `to_fixed_bytes` for wrapped types
71#[macro_export]
72macro_rules! impl_to_fixed_bytes {
73    ($struct_name:ident, $type:ty) => {
74        impl $struct_name {
75            pub(crate) fn to_fixed_bytes(self) -> [u8; core::mem::size_of::<$type>()] {
76                let bytes: [u8; core::mem::size_of::<$type>()] = self.0.to_le_bytes();
77                bytes
78            }
79        }
80    };
81}
82
83/// send a transaction and wait for confirmation before returning the tx receipt and block included.
84///
85/// # NOTE:
86/// - `wait_for_transaction_to_be_mined` is removed thanks to alloy's better builtin PendingTransaction await
87/// - DON'T use this if you want parse the exact revert reason/type, since this func will only give err msg like: "custom error 0x23b0db14",
88///   instead, follow <https://docs.rs/alloy/0.12.5/alloy/contract/enum.Error.html#method.as_decoded_interface_error> to pattern-match err type
89pub async fn contract_send<P, C>(
90    call: &SolCallBuilder<P, C>,
91) -> Result<(TransactionReceipt, u64), anyhow::Error>
92where
93    P: Provider,
94    C: SolCall,
95{
96    let pending = match call.send().await {
97        Ok(pending) => pending,
98        Err(err) => {
99            if let Some(e) = err.as_decoded_interface_error::<GenericContractError>() {
100                tracing::error!("contract err: {:?}", e);
101            }
102            return Err(anyhow!("error sending transaction: {:?}", err));
103        },
104    };
105
106    let hash = pending.tx_hash().to_owned();
107    tracing::info!("submitted contract call 0x{:x}", hash);
108
109    let receipt = match pending.get_receipt().await {
110        Ok(r) => r,
111        Err(err) => {
112            return Err(anyhow!(
113                "contract call 0x{hash:x}: error getting transaction receipt: {err}"
114            ));
115        },
116    };
117
118    // If a transaction is mined and we get a receipt for it, the block number should _always_ be
119    // set. If it is not, something has gone horribly wrong with the RPC.
120    let block_number = receipt
121        .block_number()
122        .expect("transaction mined but block number not set");
123    Ok((receipt, block_number))
124}
125
126#[cfg(test)]
127mod test {
128    use alloy::{primitives::I256, sol};
129    use anyhow::Result;
130    use committable::RawCommitmentBuilder;
131
132    use super::*;
133
134    // contract for tests, credit: <https://alloy.rs/examples/sol-macro/events_errors.html>
135    sol! {
136        #[allow(missing_docs)]
137        #[sol(rpc, bytecode = "608060405260008055348015601357600080fd5b506103e9806100236000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632baeceb71461005c5780632ccbdbca1461006657806361bc221a14610070578063c3e8b5ca1461008e578063d09de08a14610098575b600080fd5b6100646100a2565b005b61006e610103565b005b61007861013e565b60405161008591906101f9565b60405180910390f35b610096610144565b005b6100a061017f565b005b60016000808282546100b49190610243565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167fdc69c403b972fc566a14058b3b18e1513da476de6ac475716e489fae0cbe4a2660405160405180910390a3565b6040517f23b0db14000000000000000000000000000000000000000000000000000000008152600401610135906102e3565b60405180910390fd5b60005481565b6040517fa5f9ec670000000000000000000000000000000000000000000000000000000081526004016101769061034f565b60405180910390fd5b6001600080828254610191919061036f565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167ff6d1d8d205b41f9fb9549900a8dba5d669d68117a3a2b88c1ebc61163e8117ba60405160405180910390a3565b6000819050919050565b6101f3816101e0565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e826101e0565b9150610259836101e0565b92508282039050818112600084121682821360008512151617156102805761027f610214565b5b92915050565b600082825260208201905092915050565b7f4572726f72204100000000000000000000000000000000000000000000000000600082015250565b60006102cd600783610286565b91506102d882610297565b602082019050919050565b600060208201905081810360008301526102fc816102c0565b9050919050565b7f4572726f72204200000000000000000000000000000000000000000000000000600082015250565b6000610339600783610286565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600061037a826101e0565b9150610385836101e0565b9250828201905082811215600083121683821260008412151617156103ad576103ac610214565b5b9291505056fea2646970667358221220a878a3c1da1a1170e4496cdbc63bd5ed1587374bcd6cf6d4f1d5b88fa981795d64736f6c63430008190033")]
138        contract CounterWithError {
139            int256 public counter = 0;
140
141            // Events - using `Debug` to print the events
142            #[derive(Debug)]
143            event Increment(address indexed by, int256 indexed value);
144            #[derive(Debug)]
145            event Decrement(address indexed by, int256 indexed value);
146
147            // Custom Error
148            #[derive(Debug)]
149            error ErrorA(string message);
150            #[derive(Debug)]
151            error ErrorB(string message);
152
153            // Functions
154            function increment() public {
155                counter += 1;
156                emit Increment(msg.sender, counter);
157            }
158
159            function decrement() public {
160                counter -= 1;
161                emit Decrement(msg.sender, counter);
162            }
163
164            function revertA() public pure {
165                revert ErrorA("Error A");
166            }
167
168            function revertB() public pure {
169                revert ErrorB("Error B");
170            }
171        }
172    }
173
174    struct TestCommittable;
175
176    impl Committable for TestCommittable {
177        fn commit(&self) -> Commitment<Self> {
178            RawCommitmentBuilder::new("TestCommittable").finalize()
179        }
180    }
181
182    #[test]
183    fn test_commitment_to_u256_round_trip() {
184        assert_eq!(
185            TestCommittable.commit(),
186            u256_to_commitment(commitment_to_u256(TestCommittable.commit())).unwrap()
187        );
188    }
189
190    #[test_log::test(tokio::test)]
191    async fn test_contract_send() -> Result<()> {
192        let provider = ProviderBuilder::new().connect_anvil_with_wallet();
193        let contract = CounterWithError::deploy(provider.clone()).await?;
194
195        // test normal contract sending should success
196        let inc_call = contract.increment();
197        let (receipt, block_num) = contract_send(&inc_call).await?;
198        assert_eq!(block_num, 2); // 1 for deployment, 1 for this send
199        assert!(receipt.inner.is_success());
200        assert_eq!(contract.counter().call().await?, I256::ONE);
201
202        // test contract revert will return useful error message
203        let revert_call = contract.revertA();
204        assert!(contract_send(&revert_call).await.is_err());
205
206        Ok(())
207    }
208}