|
12 | 12 | // See the License for the specific language governing permissions and
|
13 | 13 | // limitations under the License.
|
14 | 14 |
|
| 15 | +use alloy::network::Ethereum; |
| 16 | +use alloy::providers::Provider; |
| 17 | +use alloy::transports::http::Http; |
15 | 18 | use alloy::{
|
16 |
| - network::EthereumWallet, node_bindings::Anvil, primitives::U256, providers::ProviderBuilder, |
| 19 | + network::EthereumWallet, |
| 20 | + node_bindings::{Anvil, AnvilInstance}, |
| 21 | + primitives::U256, |
| 22 | + providers::ProviderBuilder, |
17 | 23 | signers::local::PrivateKeySigner,
|
18 | 24 | };
|
19 | 25 | use alloy_sol_types::{sol, SolCall};
|
20 | 26 | use blobstream0_core::{post_batch, prove_block_range};
|
21 |
| -use blobstream0_primitives::IBlobstream::{self, BinaryMerkleProof, DataRootTuple}; |
| 27 | +use blobstream0_primitives::IBlobstream::{ |
| 28 | + self, BinaryMerkleProof, DataRootTuple, IBlobstreamInstance, |
| 29 | +}; |
22 | 30 | use reqwest::header;
|
23 | 31 | use serde::{Deserialize, Serialize};
|
24 | 32 | use serde_with::{base64::Base64, serde_as, DisplayFromStr};
|
@@ -57,13 +65,18 @@ struct DataRootInclusionResponse {
|
57 | 65 | aunts: Vec<Vec<u8>>,
|
58 | 66 | }
|
59 | 67 |
|
60 |
| -#[tokio::test(flavor = "multi_thread")] |
61 |
| -async fn e2e_basic_range() -> anyhow::Result<()> { |
| 68 | +async fn setup_test_environment() -> anyhow::Result<( |
| 69 | + AnvilInstance, |
| 70 | + IBlobstreamInstance< |
| 71 | + Http<reqwest::Client>, |
| 72 | + impl Provider<Http<reqwest::Client>, Ethereum>, |
| 73 | + Ethereum, |
| 74 | + >, |
| 75 | +)> { |
62 | 76 | // Set dev mode for test.
|
63 | 77 | std::env::set_var("RISC0_DEV_MODE", "true");
|
64 | 78 |
|
65 | 79 | // Spin up a local Anvil node.
|
66 |
| - // Ensure `anvil` is available in $PATH. |
67 | 80 | let anvil = Anvil::new().try_spawn()?;
|
68 | 81 |
|
69 | 82 | // Set up signer from the first default Anvil account (Alice).
|
@@ -108,8 +121,16 @@ async fn e2e_basic_range() -> anyhow::Result<()> {
|
108 | 121 | )
|
109 | 122 | .await?;
|
110 | 123 | // Pretend as if the proxy is the contract itself, requests forwarded to implementation.
|
111 |
| - let contract = IBlobstream::new(contract.address().clone(), provider.clone()); |
| 124 | + let contract = IBlobstream::new(contract.address().clone(), provider); |
| 125 | + |
| 126 | + Ok((anvil, contract)) |
| 127 | +} |
| 128 | + |
| 129 | +#[tokio::test(flavor = "multi_thread")] |
| 130 | +async fn e2e_basic_range() -> anyhow::Result<()> { |
| 131 | + let (_anvil, contract) = setup_test_environment().await?; |
112 | 132 |
|
| 133 | + let tm_client = Arc::new(HttpClient::new(CELESTIA_RPC_URL)?); |
113 | 134 | let receipt =
|
114 | 135 | prove_block_range(tm_client.clone(), BATCH_START as u64..BATCH_END as u64).await?;
|
115 | 136 |
|
@@ -167,3 +188,128 @@ async fn e2e_basic_range() -> anyhow::Result<()> {
|
167 | 188 |
|
168 | 189 | Ok(())
|
169 | 190 | }
|
| 191 | + |
| 192 | +#[tokio::test(flavor = "multi_thread")] |
| 193 | +async fn test_admin_functions() -> anyhow::Result<()> { |
| 194 | + let (_anvil, contract) = setup_test_environment().await?; |
| 195 | + |
| 196 | + // Test adminSetImageId |
| 197 | + let new_image_id = [1u8; 32]; |
| 198 | + contract |
| 199 | + .adminSetImageId(new_image_id.into()) |
| 200 | + .send() |
| 201 | + .await? |
| 202 | + .watch() |
| 203 | + .await?; |
| 204 | + let current_image_id = contract.imageId().call().await?; |
| 205 | + assert_eq!(current_image_id._0, new_image_id); |
| 206 | + |
| 207 | + // Test adminSetVerifier |
| 208 | + let new_verifier = MockVerifier::deploy(contract.provider(), [1, 1, 1, 1].into()).await?; |
| 209 | + contract |
| 210 | + .adminSetVerifier(new_verifier.address().clone()) |
| 211 | + .send() |
| 212 | + .await? |
| 213 | + .watch() |
| 214 | + .await?; |
| 215 | + let current_verifier = contract.verifier().call().await?; |
| 216 | + assert_eq!(current_verifier._0, *new_verifier.address()); |
| 217 | + |
| 218 | + // Test adminSetTrustedState |
| 219 | + let new_trusted_hash = [2u8; 32]; |
| 220 | + let new_trusted_height = 100u64; |
| 221 | + contract |
| 222 | + .adminSetTrustedState(new_trusted_hash.into(), new_trusted_height.into()) |
| 223 | + .send() |
| 224 | + .await? |
| 225 | + .watch() |
| 226 | + .await?; |
| 227 | + let current_trusted_hash = contract.latestBlockHash().call().await?; |
| 228 | + let current_trusted_height = contract.latestHeight().call().await?; |
| 229 | + assert_eq!(current_trusted_hash._0, new_trusted_hash); |
| 230 | + assert_eq!(current_trusted_height._0, new_trusted_height); |
| 231 | + |
| 232 | + Ok(()) |
| 233 | +} |
| 234 | + |
| 235 | +#[tokio::test(flavor = "multi_thread")] |
| 236 | +async fn test_contract_upgrade() -> anyhow::Result<()> { |
| 237 | + let (_anvil, contract) = setup_test_environment().await?; |
| 238 | + |
| 239 | + // Deploy a new implementation |
| 240 | + let new_implementation = IBlobstream::deploy(contract.provider()).await?; |
| 241 | + |
| 242 | + // Upgrade the contract |
| 243 | + contract |
| 244 | + .upgradeToAndCall(new_implementation.address().clone(), vec![].into()) |
| 245 | + .send() |
| 246 | + .await? |
| 247 | + .watch() |
| 248 | + .await?; |
| 249 | + |
| 250 | + // Verify the upgrade |
| 251 | + let implementation_slot: U256 = U256::from_str_radix( |
| 252 | + "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc", |
| 253 | + 16, |
| 254 | + ) |
| 255 | + .unwrap(); |
| 256 | + let current_implementation = contract |
| 257 | + .provider() |
| 258 | + .get_storage_at(contract.address().clone(), implementation_slot) |
| 259 | + .await?; |
| 260 | + assert_eq!( |
| 261 | + ¤t_implementation.to_be_bytes::<32>()[12..32], |
| 262 | + new_implementation.address().as_slice() |
| 263 | + ); |
| 264 | + |
| 265 | + // Test that the new implementation works as normal |
| 266 | + let tm_client = Arc::new(HttpClient::new(CELESTIA_RPC_URL)?); |
| 267 | + let receipt = |
| 268 | + prove_block_range(tm_client.clone(), BATCH_START as u64..BATCH_END as u64).await?; |
| 269 | + |
| 270 | + post_batch(&contract, &receipt).await?; |
| 271 | + |
| 272 | + let height = contract.latestHeight().call().await?; |
| 273 | + assert_eq!(height._0, BATCH_END as u64 - 1); |
| 274 | + |
| 275 | + Ok(()) |
| 276 | +} |
| 277 | + |
| 278 | +#[tokio::test(flavor = "multi_thread")] |
| 279 | +async fn test_ownership_transfer() -> anyhow::Result<()> { |
| 280 | + let (anvil, contract) = setup_test_environment().await?; |
| 281 | + |
| 282 | + // Get the initial owner |
| 283 | + let initial_owner = contract.owner().call().await?; |
| 284 | + assert_eq!(initial_owner._0, anvil.addresses()[0]); |
| 285 | + |
| 286 | + // Transfer ownership |
| 287 | + let new_owner = anvil.addresses()[1]; |
| 288 | + contract |
| 289 | + .transferOwnership(new_owner) |
| 290 | + .send() |
| 291 | + .await? |
| 292 | + .watch() |
| 293 | + .await?; |
| 294 | + |
| 295 | + // Accept ownership (need to switch to the new owner's wallet) |
| 296 | + let new_owner_signer: PrivateKeySigner = anvil.keys()[1].clone().into(); |
| 297 | + let new_owner_wallet = EthereumWallet::from(new_owner_signer); |
| 298 | + let new_owner_provider = ProviderBuilder::new() |
| 299 | + .with_recommended_fillers() |
| 300 | + .wallet(new_owner_wallet) |
| 301 | + .on_http(anvil.endpoint().parse()?); |
| 302 | + let contract_as_new_owner = IBlobstream::new(contract.address().clone(), new_owner_provider); |
| 303 | + contract_as_new_owner |
| 304 | + .acceptOwnership() |
| 305 | + .send() |
| 306 | + .await? |
| 307 | + .watch() |
| 308 | + .await?; |
| 309 | + |
| 310 | + // Verify the new owner |
| 311 | + let final_owner = contract.owner().call().await?; |
| 312 | + assert_eq!(final_owner._0, new_owner); |
| 313 | + |
| 314 | + Ok(()) |
| 315 | +} |
0 commit comments