1#[cfg(feature = "full")]
2use std::time::Duration;
3
4use alloy::primitives::U256;
5#[cfg(feature = "full")]
6use alloy::{
7 contract::SolCallBuilder,
8 network::ReceiptResponse,
9 providers::{Provider, ProviderBuilder},
10 rpc::types::TransactionReceipt,
11 sol_types::{GenericContractError, SolCall},
12};
13#[cfg(feature = "full")]
14use anyhow::anyhow;
15use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError};
16use committable::{Commitment, Committable};
17#[cfg(feature = "full")]
18use tokio::time::sleep;
19#[cfg(feature = "full")]
20use url::Url;
21
22pub mod build_info;
23pub mod env_compat;
24#[cfg(feature = "full")]
25pub mod logging;
26pub mod redact;
27pub mod ser;
28#[cfg(feature = "full")]
29pub mod shutdown;
30pub mod test_utils;
31
32#[cfg(feature = "full")]
33pub async fn wait_for_http(
34 url: &Url,
35 interval: Duration,
36 max_retries: usize,
37) -> Result<usize, String> {
38 let client = reqwest::Client::new();
41 for i in 0..(max_retries + 1) {
42 let res = client.get(url.clone()).send().await;
43 if res.is_ok() {
44 tracing::debug!("Connected to {url}");
45 return Ok(i);
46 }
47 tracing::debug!("Waiting for {url}, retrying in {interval:?}");
48 sleep(interval).await;
49 }
50 Err(format!("Url {url:?} not available."))
51}
52
53#[cfg(feature = "full")]
54pub async fn wait_for_rpc(
55 url: &Url,
56 interval: Duration,
57 max_retries: usize,
58) -> Result<usize, String> {
59 let retries = wait_for_http(url, interval, max_retries).await?;
60 let client = ProviderBuilder::new().connect_http(url.clone());
61 for i in retries..(max_retries + 1) {
62 if client.get_block_number().await.is_ok() {
63 tracing::debug!("JSON-RPC ready at {url}");
64 return Ok(i);
65 }
66 tracing::debug!("Waiting for JSON-RPC at {url}, retrying in {interval:?}");
67 sleep(interval).await;
68 }
69
70 Err(format!("No JSON-RPC at {url}"))
71}
72
73pub fn commitment_to_u256<T: Committable>(comm: Commitment<T>) -> U256 {
75 let mut buf = vec![];
76 comm.serialize_uncompressed(&mut buf).unwrap();
77 U256::from_le_slice(&buf)
78}
79
80pub fn u256_to_commitment<T: Committable>(comm: U256) -> Result<Commitment<T>, SerializationError> {
82 Commitment::deserialize_uncompressed_unchecked(&*comm.to_le_bytes_vec())
83}
84
85#[macro_export]
87macro_rules! impl_to_fixed_bytes {
88 ($struct_name:ident, $type:ty) => {
89 impl $struct_name {
90 pub(crate) fn to_fixed_bytes(self) -> [u8; core::mem::size_of::<$type>()] {
91 let bytes: [u8; core::mem::size_of::<$type>()] = self.0.to_le_bytes();
92 bytes
93 }
94 }
95 };
96}
97
98#[cfg(feature = "full")]
105pub async fn contract_send<P, C>(
106 call: &SolCallBuilder<P, C>,
107) -> Result<(TransactionReceipt, u64), anyhow::Error>
108where
109 P: Provider,
110 C: SolCall,
111{
112 let pending = match call.send().await {
113 Ok(pending) => pending,
114 Err(err) => {
115 if let Some(e) = err.as_decoded_interface_error::<GenericContractError>() {
116 tracing::error!("contract err: {:?}", e);
117 }
118 return Err(anyhow!("error sending transaction: {:?}", err));
119 },
120 };
121
122 let hash = pending.tx_hash().to_owned();
123 tracing::info!("submitted contract call 0x{:x}", hash);
124
125 let receipt = match pending.get_receipt().await {
126 Ok(r) => r,
127 Err(err) => {
128 return Err(anyhow!(
129 "contract call 0x{hash:x}: error getting transaction receipt: {err}"
130 ));
131 },
132 };
133
134 let block_number = receipt
137 .block_number()
138 .expect("transaction mined but block number not set");
139 Ok((receipt, block_number))
140}
141
142#[cfg(test)]
143mod test {
144 #[cfg(feature = "full")]
145 use alloy::{primitives::I256, sol};
146 #[cfg(feature = "full")]
147 use anyhow::Result;
148 use committable::RawCommitmentBuilder;
149
150 use super::*;
151
152 #[cfg(feature = "full")]
154 sol! {
155 #[allow(missing_docs)]
156 #[sol(rpc, bytecode = "608060405260008055348015601357600080fd5b506103e9806100236000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632baeceb71461005c5780632ccbdbca1461006657806361bc221a14610070578063c3e8b5ca1461008e578063d09de08a14610098575b600080fd5b6100646100a2565b005b61006e610103565b005b61007861013e565b60405161008591906101f9565b60405180910390f35b610096610144565b005b6100a061017f565b005b60016000808282546100b49190610243565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167fdc69c403b972fc566a14058b3b18e1513da476de6ac475716e489fae0cbe4a2660405160405180910390a3565b6040517f23b0db14000000000000000000000000000000000000000000000000000000008152600401610135906102e3565b60405180910390fd5b60005481565b6040517fa5f9ec670000000000000000000000000000000000000000000000000000000081526004016101769061034f565b60405180910390fd5b6001600080828254610191919061036f565b925050819055506000543373ffffffffffffffffffffffffffffffffffffffff167ff6d1d8d205b41f9fb9549900a8dba5d669d68117a3a2b88c1ebc61163e8117ba60405160405180910390a3565b6000819050919050565b6101f3816101e0565b82525050565b600060208201905061020e60008301846101ea565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061024e826101e0565b9150610259836101e0565b92508282039050818112600084121682821360008512151617156102805761027f610214565b5b92915050565b600082825260208201905092915050565b7f4572726f72204100000000000000000000000000000000000000000000000000600082015250565b60006102cd600783610286565b91506102d882610297565b602082019050919050565b600060208201905081810360008301526102fc816102c0565b9050919050565b7f4572726f72204200000000000000000000000000000000000000000000000000600082015250565b6000610339600783610286565b915061034482610303565b602082019050919050565b600060208201905081810360008301526103688161032c565b9050919050565b600061037a826101e0565b9150610385836101e0565b9250828201905082811215600083121683821260008412151617156103ad576103ac610214565b5b9291505056fea2646970667358221220a878a3c1da1a1170e4496cdbc63bd5ed1587374bcd6cf6d4f1d5b88fa981795d64736f6c63430008190033")]
157 contract CounterWithError {
158 int256 public counter = 0;
159
160 #[derive(Debug)]
162 event Increment(address indexed by, int256 indexed value);
163 #[derive(Debug)]
164 event Decrement(address indexed by, int256 indexed value);
165
166 #[derive(Debug)]
168 error ErrorA(string message);
169 #[derive(Debug)]
170 error ErrorB(string message);
171
172 function increment() public {
174 counter += 1;
175 emit Increment(msg.sender, counter);
176 }
177
178 function decrement() public {
179 counter -= 1;
180 emit Decrement(msg.sender, counter);
181 }
182
183 function revertA() public pure {
184 revert ErrorA("Error A");
185 }
186
187 function revertB() public pure {
188 revert ErrorB("Error B");
189 }
190 }
191 }
192
193 struct TestCommittable;
194
195 impl Committable for TestCommittable {
196 fn commit(&self) -> Commitment<Self> {
197 RawCommitmentBuilder::new("TestCommittable").finalize()
198 }
199 }
200
201 #[test]
202 fn test_commitment_to_u256_round_trip() {
203 assert_eq!(
204 TestCommittable.commit(),
205 u256_to_commitment(commitment_to_u256(TestCommittable.commit())).unwrap()
206 );
207 }
208
209 #[cfg(feature = "full")]
210 #[test_log::test(tokio::test)]
211 async fn test_contract_send() -> Result<()> {
212 let provider = ProviderBuilder::new().connect_anvil_with_wallet();
213 let contract = CounterWithError::deploy(provider.clone()).await?;
214
215 let inc_call = contract.increment();
217 let (receipt, block_num) = contract_send(&inc_call).await?;
218 assert_eq!(block_num, 2); assert!(receipt.inner.is_success());
220 assert_eq!(contract.counter().call().await?, I256::ONE);
221
222 let revert_call = contract.revertA();
224 assert!(contract_send(&revert_call).await.is_err());
225
226 Ok(())
227 }
228}