1use alloy::{
2 network::Ethereum,
3 primitives::Address,
4 providers::{PendingTransactionBuilder, Provider},
5};
6use anyhow::Result;
7use hotshot_contract_adapter::{
8 evm::DecodeRevert as _,
9 sol_types::StakeTableV2::{self, StakeTableV2Errors},
10 stake_table::StakeTableContractVersion,
11};
12
13use crate::{
14 metadata::MetadataUri,
15 parse::Commission,
16 signature::{NodeSignatures, NodeSignaturesSol},
17};
18
19pub async fn register_validator(
20 provider: impl Provider,
21 stake_table_addr: Address,
22 commission: Commission,
23 metadata_uri: MetadataUri,
24 payload: NodeSignatures,
25) -> Result<PendingTransactionBuilder<Ethereum>> {
26 tracing::info!(
27 "register validator {} with commission {commission}",
28 payload.address
29 );
30 let stake_table = StakeTableV2::new(stake_table_addr, provider);
34 let sol_payload = NodeSignaturesSol::from(payload);
35
36 let version = stake_table.getVersion().call().await?.try_into()?;
37 Ok(match version {
41 StakeTableContractVersion::V1 => stake_table
42 .registerValidator(
43 sol_payload.bls_vk,
44 sol_payload.schnorr_vk,
45 sol_payload.bls_signature.into(),
46 commission.to_evm(),
47 )
48 .send()
49 .await
50 .maybe_decode_revert::<StakeTableV2Errors>()?,
51 StakeTableContractVersion::V2 => stake_table
52 .registerValidatorV2(
53 sol_payload.bls_vk,
54 sol_payload.schnorr_vk,
55 sol_payload.bls_signature.into(),
56 sol_payload.schnorr_signature.into(),
57 commission.to_evm(),
58 metadata_uri.to_string(),
59 )
60 .send()
61 .await
62 .maybe_decode_revert::<StakeTableV2Errors>()?,
63 })
64}
65
66pub async fn update_consensus_keys(
67 provider: impl Provider,
68 stake_table_addr: Address,
69 payload: NodeSignatures,
70) -> Result<PendingTransactionBuilder<Ethereum>> {
71 let stake_table = StakeTableV2::new(stake_table_addr, provider);
75 let sol_payload = NodeSignaturesSol::from(payload);
76
77 let version = stake_table.getVersion().call().await?.try_into()?;
81 Ok(match version {
82 StakeTableContractVersion::V1 => stake_table
83 .updateConsensusKeys(
84 sol_payload.bls_vk,
85 sol_payload.schnorr_vk,
86 sol_payload.bls_signature.into(),
87 )
88 .send()
89 .await
90 .maybe_decode_revert::<StakeTableV2Errors>()?,
91 StakeTableContractVersion::V2 => stake_table
92 .updateConsensusKeysV2(
93 sol_payload.bls_vk,
94 sol_payload.schnorr_vk,
95 sol_payload.bls_signature.into(),
96 sol_payload.schnorr_signature.into(),
97 )
98 .send()
99 .await
100 .maybe_decode_revert::<StakeTableV2Errors>()?,
101 })
102}
103
104pub async fn deregister_validator(
105 provider: impl Provider,
106 stake_table_addr: Address,
107) -> Result<PendingTransactionBuilder<Ethereum>> {
108 let stake_table = StakeTableV2::new(stake_table_addr, provider);
109 stake_table
110 .deregisterValidator()
111 .send()
112 .await
113 .maybe_decode_revert::<StakeTableV2Errors>()
114}
115
116pub async fn update_commission(
117 provider: impl Provider,
118 stake_table_addr: Address,
119 new_commission: Commission,
120) -> Result<PendingTransactionBuilder<Ethereum>> {
121 let stake_table = StakeTableV2::new(stake_table_addr, provider);
122 stake_table
123 .updateCommission(new_commission.to_evm())
124 .send()
125 .await
126 .maybe_decode_revert::<StakeTableV2Errors>()
127}
128
129pub async fn update_metadata_uri(
130 provider: impl Provider,
131 stake_table_addr: Address,
132 metadata_uri: MetadataUri,
133) -> Result<PendingTransactionBuilder<Ethereum>> {
134 let stake_table = StakeTableV2::new(stake_table_addr, provider);
135 stake_table
136 .updateMetadataUri(metadata_uri.to_string())
137 .send()
138 .await
139 .maybe_decode_revert::<StakeTableV2Errors>()
140}
141
142pub async fn fetch_commission(
143 provider: impl Provider,
144 stake_table_addr: Address,
145 validator: Address,
146) -> Result<Commission> {
147 let stake_table = StakeTableV2::new(stake_table_addr, provider);
148 let version: StakeTableContractVersion = stake_table.getVersion().call().await?.try_into()?;
149 if matches!(version, StakeTableContractVersion::V1) {
150 anyhow::bail!("fetching commission is not supported with stake table V1");
151 }
152 Ok(stake_table
153 .commissionTracking(validator)
154 .call()
155 .await?
156 .commission
157 .try_into()?)
158}
159
160#[cfg(test)]
161mod test {
162 use alloy::{primitives::U256, providers::WalletProvider as _};
163 use espresso_contract_deployer::build_provider;
164 use espresso_types::{
165 v0_3::{Fetcher, StakeTableEvent},
166 L1Client,
167 };
168 use hotshot_contract_adapter::{
169 sol_types::{EdOnBN254PointSol, G1PointSol, G2PointSol},
170 stake_table::{sign_address_bls, sign_address_schnorr, StateSignatureSol},
171 };
172 use rand::{rngs::StdRng, SeedableRng as _};
173 use rstest::rstest;
174
175 use super::*;
176 use crate::{deploy::TestSystem, receipt::ReceiptExt};
177
178 #[tokio::test]
179 async fn test_register_validator() -> Result<()> {
180 let system = TestSystem::deploy().await?;
181 let validator_address = system.deployer_address;
182 let payload = NodeSignatures::create(
183 validator_address,
184 &system.bls_key_pair,
185 &system.state_key_pair,
186 );
187
188 let metadata_uri = "https://example.com/metadata".parse()?;
189 let receipt = register_validator(
190 &system.provider,
191 system.stake_table,
192 system.commission,
193 metadata_uri,
194 payload,
195 )
196 .await?
197 .assert_success()
198 .await?;
199
200 let event = receipt
201 .decoded_log::<StakeTableV2::ValidatorRegisteredV2>()
202 .unwrap();
203 assert_eq!(event.account, validator_address);
204 assert_eq!(event.commission, system.commission.to_evm());
205 assert_eq!(event.metadataUri, "https://example.com/metadata");
206
207 assert_eq!(event.blsVK, system.bls_key_pair.ver_key().into());
208 assert_eq!(event.schnorrVK, system.state_key_pair.ver_key().into());
209
210 event.data.authenticate()?;
211 Ok(())
212 }
213
214 #[rstest]
215 #[case(StakeTableContractVersion::V1)]
216 #[case(StakeTableContractVersion::V2)]
217 #[tokio::test]
218 async fn test_deregister_validator(#[case] version: StakeTableContractVersion) -> Result<()> {
219 let system = TestSystem::deploy_version(version).await?;
220 system.register_validator().await?;
221
222 let receipt = deregister_validator(&system.provider, system.stake_table)
223 .await?
224 .assert_success()
225 .await?;
226
227 match version {
228 StakeTableContractVersion::V1 => {
229 let event = receipt
230 .decoded_log::<StakeTableV2::ValidatorExit>()
231 .unwrap();
232 assert_eq!(event.validator, system.deployer_address);
233 },
234 StakeTableContractVersion::V2 => {
235 let event = receipt
236 .decoded_log::<StakeTableV2::ValidatorExitV2>()
237 .unwrap();
238 assert_eq!(event.validator, system.deployer_address);
239 let block = system
240 .provider
241 .get_block_by_number(receipt.block_number.unwrap().into())
242 .await?
243 .unwrap();
244 let expected_unlock = block.header.timestamp + system.exit_escrow_period.as_secs();
245 assert_eq!(event.unlocksAt, U256::from(expected_unlock));
246 },
247 }
248
249 Ok(())
250 }
251
252 #[tokio::test]
253 async fn test_update_consensus_keys() -> Result<()> {
254 let system = TestSystem::deploy().await?;
255 system.register_validator().await?;
256 let validator_address = system.deployer_address;
257 let mut rng = StdRng::from_seed([43u8; 32]);
258 let (_, new_bls, new_schnorr) = TestSystem::gen_keys(&mut rng);
259 let payload = NodeSignatures::create(validator_address, &new_bls, &new_schnorr);
260
261 let receipt = update_consensus_keys(&system.provider, system.stake_table, payload)
262 .await?
263 .assert_success()
264 .await?;
265
266 let event = receipt
267 .decoded_log::<StakeTableV2::ConsensusKeysUpdatedV2>()
268 .unwrap();
269 assert_eq!(event.account, system.deployer_address);
270
271 assert_eq!(event.blsVK, new_bls.ver_key().into());
272 assert_eq!(event.schnorrVK, new_schnorr.ver_key().into());
273
274 event.data.authenticate()?;
275
276 Ok(())
277 }
278
279 #[tokio::test]
280 async fn test_update_commission() -> Result<()> {
281 let system = TestSystem::deploy().await?;
282
283 let stake_table = StakeTableV2::new(system.stake_table, &system.provider);
285 stake_table
286 .setMinCommissionUpdateInterval(U256::from(1)) .send()
288 .await?
289 .assert_success()
290 .await?;
291
292 system.register_validator().await?;
293 let validator_address = system.deployer_address;
294 let new_commission = Commission::try_from("10.50")?;
295
296 system.anvil_increase_time(U256::from(2)).await?;
298
299 let receipt = update_commission(&system.provider, system.stake_table, new_commission)
300 .await?
301 .assert_success()
302 .await?;
303
304 let event = receipt
305 .decoded_log::<StakeTableV2::CommissionUpdated>()
306 .unwrap();
307 assert_eq!(event.validator, validator_address);
308 assert_eq!(event.newCommission, new_commission.to_evm());
309
310 let fetched_commission =
311 fetch_commission(&system.provider, system.stake_table, validator_address).await?;
312 assert_eq!(fetched_commission, new_commission);
313
314 Ok(())
315 }
316
317 #[tokio::test]
321 async fn test_integration_unauthenticated_validator_registered_events_removed() -> Result<()> {
322 let system = TestSystem::deploy().await?;
323
324 system.register_validator().await?;
326
327 let provider = build_provider(
330 "test test test test test test test test test test test junk",
331 1,
332 system.rpc_url.clone(),
333 None,
334 );
335 let validator_address = provider.default_signer_address();
336 let (_, bls_key_pair, schnorr_key_pair) =
337 TestSystem::gen_keys(&mut StdRng::from_seed([1u8; 32]));
338 let (_, _, other_schnorr_key_pair) =
339 TestSystem::gen_keys(&mut StdRng::from_seed([2u8; 32]));
340
341 let bls_vk = G2PointSol::from(bls_key_pair.ver_key());
342 let bls_sig = G1PointSol::from(sign_address_bls(&bls_key_pair, validator_address));
343 let schnorr_vk = EdOnBN254PointSol::from(schnorr_key_pair.ver_key());
344
345 let schnorr_sig_other_key = StateSignatureSol::from(sign_address_schnorr(
347 &other_schnorr_key_pair,
348 validator_address,
349 ));
350
351 let stake_table = StakeTableV2::new(system.stake_table, provider);
352
353 let receipt = stake_table
355 .registerValidatorV2(
356 bls_vk,
357 schnorr_vk,
358 bls_sig.into(),
359 schnorr_sig_other_key.into(),
360 Commission::try_from("12.34")?.to_evm(),
361 "https://example.com/metadata".to_string(),
362 )
363 .send()
364 .await
365 .maybe_decode_revert::<StakeTableV2Errors>()?
366 .assert_success()
367 .await?;
368
369 let l1 = L1Client::new(vec![system.rpc_url])?;
370 let events = Fetcher::fetch_events_from_contract(
371 l1,
372 system.stake_table,
373 Some(0),
374 receipt.block_number.unwrap(),
375 )
376 .await?;
377
378 assert_eq!(events.len(), 1);
380 match events[0].1.clone() {
381 StakeTableEvent::RegisterV2(event) => {
382 assert_eq!(event.account, system.deployer_address);
383 },
384 _ => panic!("expected RegisterV2 event"),
385 }
386 Ok(())
387 }
388
389 #[tokio::test]
393 async fn test_integration_unauthenticated_update_consensus_keys_events_removed() -> Result<()> {
394 let system = TestSystem::deploy().await?;
395
396 system.register_validator().await?;
398 let validator_address = system.deployer_address;
399
400 let (_, new_bls_key_pair, new_schnorr_key_pair) =
403 TestSystem::gen_keys(&mut StdRng::from_seed([1u8; 32]));
404 let (_, _, other_schnorr_key_pair) =
405 TestSystem::gen_keys(&mut StdRng::from_seed([2u8; 32]));
406
407 let bls_vk = G2PointSol::from(new_bls_key_pair.ver_key());
408 let bls_sig = G1PointSol::from(sign_address_bls(&new_bls_key_pair, validator_address));
409 let schnorr_vk = EdOnBN254PointSol::from(new_schnorr_key_pair.ver_key());
410
411 let schnorr_sig_other_key = StateSignatureSol::from(sign_address_schnorr(
413 &other_schnorr_key_pair,
414 validator_address,
415 ))
416 .into();
417
418 let stake_table = StakeTableV2::new(system.stake_table, system.provider);
419
420 let receipt = stake_table
422 .updateConsensusKeysV2(bls_vk, schnorr_vk, bls_sig.into(), schnorr_sig_other_key)
423 .send()
424 .await
425 .maybe_decode_revert::<StakeTableV2Errors>()?
426 .assert_success()
427 .await?;
428
429 let l1 = L1Client::new(vec![system.rpc_url])?;
430 let events = Fetcher::fetch_events_from_contract(
431 l1,
432 system.stake_table,
433 Some(0),
434 receipt.block_number.unwrap(),
435 )
436 .await?;
437
438 assert_eq!(events.len(), 1);
440 match events[0].1.clone() {
441 StakeTableEvent::RegisterV2(event) => {
442 assert_eq!(event.account, system.deployer_address);
443 },
444 _ => panic!("expected RegisterV2 event"),
445 }
446
447 println!("Events: {events:?}");
448
449 Ok(())
450 }
451
452 #[tokio::test]
453 async fn test_update_metadata_uri() -> Result<()> {
454 let system = TestSystem::deploy().await?;
455 system.register_validator().await?;
456
457 let new_uri: MetadataUri = "https://example.com/updated".parse()?;
458 let receipt = update_metadata_uri(&system.provider, system.stake_table, new_uri.clone())
459 .await?
460 .assert_success()
461 .await?;
462
463 let event = receipt
464 .decoded_log::<StakeTableV2::MetadataUriUpdated>()
465 .unwrap();
466 assert_eq!(event.validator, system.deployer_address);
467 assert_eq!(event.metadataUri, new_uri.to_string());
468
469 Ok(())
470 }
471
472 #[tokio::test]
473 async fn test_register_validator_with_empty_metadata_uri() -> Result<()> {
474 let system = TestSystem::deploy().await?;
475 let validator_address = system.deployer_address;
476 let payload = NodeSignatures::create(
477 validator_address,
478 &system.bls_key_pair,
479 &system.state_key_pair,
480 );
481
482 let metadata_uri = MetadataUri::empty();
483 let receipt = register_validator(
484 &system.provider,
485 system.stake_table,
486 system.commission,
487 metadata_uri,
488 payload,
489 )
490 .await?
491 .assert_success()
492 .await?;
493
494 let event = receipt
495 .decoded_log::<StakeTableV2::ValidatorRegisteredV2>()
496 .unwrap();
497 assert_eq!(event.account, validator_address);
498 assert_eq!(event.commission, system.commission.to_evm());
499 assert_eq!(event.metadataUri, "");
500
501 Ok(())
502 }
503
504 #[tokio::test]
505 async fn test_update_metadata_uri_to_empty() -> Result<()> {
506 let system = TestSystem::deploy().await?;
507 system.register_validator().await?;
508
509 let metadata_uri = MetadataUri::empty();
510 let receipt = update_metadata_uri(&system.provider, system.stake_table, metadata_uri)
511 .await?
512 .assert_success()
513 .await?;
514
515 let event = receipt
516 .decoded_log::<StakeTableV2::MetadataUriUpdated>()
517 .unwrap();
518 assert_eq!(event.validator, system.deployer_address);
519 assert_eq!(event.metadataUri, "");
520
521 Ok(())
522 }
523}