1use battleware_types::{
2 execution::{
3 Account, Creature, Event, Instruction, Key, Leaderboard, Outcome, Output, Transaction,
4 Value, LOBBY_EXPIRY, MAX_BATTLE_ROUNDS, MAX_LOBBY_SIZE, MOVE_EXPIRY, TOTAL_MOVES,
5 },
6 Seed,
7};
8use bytes::{Buf, BufMut};
9use commonware_codec::{Encode, EncodeSize, Error, Read, ReadExt, Write};
10use commonware_consensus::threshold_simplex::types::View;
11use commonware_cryptography::{
12 bls12381::{
13 primitives::variant::{MinSig, Variant},
14 tle::{decrypt, Ciphertext},
15 },
16 ed25519::PublicKey,
17 sha256::{Digest, Sha256},
18 Hasher,
19};
20#[cfg(feature = "parallel")]
21use commonware_runtime::ThreadPool;
22use commonware_runtime::{Clock, Metrics, Spawner, Storage};
23use commonware_storage::{adb::any::variable::Any, translator::Translator};
24use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
25#[cfg(feature = "parallel")]
26use rayon::iter::{IntoParallelIterator, ParallelIterator};
27use std::{
28 collections::{BTreeMap, BTreeSet, HashMap, HashSet},
29 future::Future,
30};
31
32mod elo;
33mod fixed;
34pub mod state_transition;
35
36#[cfg(any(test, feature = "mocks"))]
37pub mod mocks;
38
39pub type Adb<E, T> = Any<E, Digest, Value, Sha256, T>;
40
41pub trait State {
42 fn get(&self, key: &Key) -> impl Future<Output = Option<Value>>;
43 fn insert(&mut self, key: Key, value: Value) -> impl Future<Output = ()>;
44 fn delete(&mut self, key: &Key) -> impl Future<Output = ()>;
45
46 fn apply(&mut self, changes: Vec<(Key, Status)>) -> impl Future<Output = ()> {
47 async {
48 for (key, status) in changes {
49 match status {
50 Status::Update(value) => self.insert(key, value).await,
51 Status::Delete => self.delete(&key).await,
52 }
53 }
54 }
55 }
56}
57
58impl<E: Spawner + Metrics + Clock + Storage, T: Translator> State for Adb<E, T> {
59 async fn get(&self, key: &Key) -> Option<Value> {
60 let key = Sha256::hash(&key.encode());
61 self.get(&key).await.unwrap()
62 }
63
64 async fn insert(&mut self, key: Key, value: Value) {
65 let key = Sha256::hash(&key.encode());
66 self.update(key, value).await.unwrap();
67 }
68
69 async fn delete(&mut self, key: &Key) {
70 let key = Sha256::hash(&key.encode());
71 self.delete(key).await.unwrap();
72 }
73}
74
75#[derive(Default)]
76pub struct Memory {
77 state: HashMap<Key, Value>,
78}
79
80impl State for Memory {
81 async fn get(&self, key: &Key) -> Option<Value> {
82 self.state.get(key).cloned()
83 }
84
85 async fn insert(&mut self, key: Key, value: Value) {
86 self.state.insert(key, value);
87 }
88
89 async fn delete(&mut self, key: &Key) {
90 self.state.remove(key);
91 }
92}
93
94#[derive(Clone)]
95#[allow(clippy::large_enum_variant)]
96pub enum Status {
97 Update(Value),
98 Delete,
99}
100
101impl Write for Status {
102 fn write(&self, writer: &mut impl BufMut) {
103 match self {
104 Status::Update(value) => {
105 0u8.write(writer);
106 value.write(writer);
107 }
108 Status::Delete => 1u8.write(writer),
109 }
110 }
111}
112
113impl Read for Status {
114 type Cfg = ();
115
116 fn read_cfg(reader: &mut impl Buf, _: &Self::Cfg) -> Result<Self, Error> {
117 let kind = u8::read(reader)?;
118 match kind {
119 0 => Ok(Status::Update(Value::read(reader)?)),
120 1 => Ok(Status::Delete),
121 _ => Err(Error::InvalidEnum(kind)),
122 }
123 }
124}
125
126impl EncodeSize for Status {
127 fn encode_size(&self) -> usize {
128 1 + match self {
129 Status::Update(value) => value.encode_size(),
130 Status::Delete => 0,
131 }
132 }
133}
134
135pub async fn nonce<S: State>(state: &S, public: &PublicKey) -> u64 {
136 let account =
137 if let Some(Value::Account(account)) = state.get(&Key::Account(public.clone())).await {
138 account
139 } else {
140 Account::default()
141 };
142 account.nonce
143}
144
145pub struct Noncer<'a, S: State> {
146 state: &'a S,
147 pending: BTreeMap<Key, Status>,
148}
149
150impl<'a, S: State> Noncer<'a, S> {
151 pub fn new(state: &'a S) -> Self {
152 Self {
153 state,
154 pending: BTreeMap::new(),
155 }
156 }
157
158 pub async fn prepare(&mut self, transaction: &Transaction) -> bool {
159 let mut account = if let Some(Value::Account(account)) =
160 self.get(&Key::Account(transaction.public.clone())).await
161 {
162 account
163 } else {
164 Account::default()
165 };
166
167 if account.nonce != transaction.nonce {
169 return false;
170 }
171
172 account.nonce += 1;
174 self.insert(
175 Key::Account(transaction.public.clone()),
176 Value::Account(account),
177 )
178 .await;
179
180 true
181 }
182}
183
184impl<'a, S: State> State for Noncer<'a, S> {
185 async fn get(&self, key: &Key) -> Option<Value> {
186 match self.pending.get(key) {
187 Some(Status::Update(value)) => Some(value.clone()),
188 Some(Status::Delete) => None,
189 None => self.state.get(key).await,
190 }
191 }
192
193 async fn insert(&mut self, key: Key, value: Value) {
194 self.pending.insert(key, Status::Update(value));
195 }
196
197 async fn delete(&mut self, key: &Key) {
198 self.pending.insert(key.clone(), Status::Delete);
199 }
200}
201
202#[derive(Hash, Eq, PartialEq)]
203#[allow(clippy::large_enum_variant)]
204enum Task {
205 Seed(Seed),
206 Decrypt(Seed, Ciphertext<MinSig>),
207}
208
209enum TaskResult {
210 Seed(bool),
211 Decrypt([u8; 32]),
212}
213
214pub struct Layer<'a, S: State> {
215 state: &'a S,
216 pending: BTreeMap<Key, Status>,
217
218 master: <MinSig as Variant>::Public,
219 namespace: Vec<u8>,
220
221 seed: Seed,
222
223 precomputations: HashMap<Task, TaskResult>,
224}
225
226impl<'a, S: State> Layer<'a, S> {
227 pub fn new(
228 state: &'a S,
229 master: <MinSig as Variant>::Public,
230 namespace: &[u8],
231 seed: Seed,
232 ) -> Self {
233 let mut verified_seeds = HashSet::new();
234 verified_seeds.insert(seed.clone());
235 Self {
236 state,
237 pending: BTreeMap::new(),
238
239 master,
240 namespace: namespace.to_vec(),
241
242 seed,
243
244 precomputations: HashMap::new(),
245 }
246 }
247
248 fn insert(&mut self, key: Key, value: Value) {
249 self.pending.insert(key, Status::Update(value));
250 }
251
252 fn delete(&mut self, key: Key) {
253 self.pending.insert(key, Status::Delete);
254 }
255
256 pub fn view(&self) -> View {
257 self.seed.view
258 }
259
260 async fn prepare(&mut self, transaction: &Transaction) -> bool {
261 let mut account = if let Some(Value::Account(account)) =
263 self.get(&Key::Account(transaction.public.clone())).await
264 {
265 account
266 } else {
267 Account::default()
268 };
269
270 if account.nonce != transaction.nonce {
272 return false;
273 }
274
275 account.nonce += 1;
277 self.insert(
278 Key::Account(transaction.public.clone()),
279 Value::Account(account),
280 );
281
282 true
283 }
284
285 async fn extract(&mut self, transaction: &Transaction) -> Vec<Task> {
286 match &transaction.instruction {
287 Instruction::Generate => vec![],
288 Instruction::Match => vec![],
289 Instruction::Move(_) => vec![],
290 Instruction::Settle(signature) => {
291 let Some(Value::Account(account)) =
293 self.get(&Key::Account(transaction.public.clone())).await
294 else {
295 return vec![];
296 };
297
298 let Some(battle) = account.battle else {
300 return vec![];
301 };
302
303 let Some(Value::Battle {
305 expiry,
306 player_a_pending,
307 player_b_pending,
308 ..
309 }) = self.get(&Key::Battle(battle)).await
310 else {
311 return vec![];
312 };
313
314 if expiry > self.seed.view {
316 return vec![];
317 }
318
319 let seed = Seed::new(expiry, *signature);
321 let mut ops = vec![Task::Seed(seed.clone())];
322 if let Some(pending) = player_a_pending {
323 ops.push(Task::Decrypt(seed.clone(), pending));
324 }
325 if let Some(pending) = player_b_pending {
326 ops.push(Task::Decrypt(seed, pending));
327 }
328
329 ops
330 }
331 }
332 }
333
334 async fn apply(&mut self, transaction: &Transaction) -> Vec<Event> {
335 let Some(Value::Account(mut account)) =
337 self.get(&Key::Account(transaction.public.clone())).await
338 else {
339 panic!("Account should exist");
340 };
341
342 let mut events = vec![];
344 match &transaction.instruction {
345 Instruction::Generate => {
346 if account.battle.is_some() {
348 return events;
349 }
350
351 let creature = Creature::new(
353 transaction.public.clone(),
354 transaction.nonce,
355 self.seed.signature,
356 );
357 account.creature = Some(creature.clone());
358
359 self.insert(
361 Key::Account(transaction.public.clone()),
362 Value::Account(account),
363 );
364 events.push(Event::Generated {
365 account: transaction.public.clone(),
366 creature,
367 });
368 }
369 Instruction::Match => {
370 if account.creature.is_none() {
372 return events;
373 }
374
375 if account.battle.is_some() {
377 return events;
378 }
379
380 let Some(Value::Lobby {
382 expiry,
383 mut players,
384 }) = self.get(&Key::Lobby).await
385 else {
386 let mut players = BTreeSet::new();
388 players.insert(transaction.public.clone());
389 let lobby = Value::Lobby {
390 expiry: self
391 .seed
392 .view
393 .checked_add(LOBBY_EXPIRY)
394 .expect("view overflow"),
395 players,
396 };
397 self.insert(Key::Lobby, lobby);
398 return events;
399 };
400
401 players.insert(transaction.public.clone());
405
406 if expiry < self.seed.view || players.len() >= MAX_LOBBY_SIZE {
408 let mut players = players.iter().collect::<Vec<_>>();
410
411 let seed = Sha256::hash(self.seed.encode().as_ref());
413 let mut rng = StdRng::from_seed(seed.as_ref().try_into().unwrap());
414 players.shuffle(&mut rng);
415
416 let iter = players.chunks_exact(2).map(|chunk| (chunk[0], chunk[1]));
418 let mut hasher = Sha256::new();
419 for (player_a, player_b) in iter {
420 hasher.update(self.seed.encode().as_ref());
422 hasher.update(player_a.as_ref());
423 hasher.update(player_b.as_ref());
424 let key = hasher.finalize();
425
426 let Some(Value::Account(mut account_a)) =
428 self.get(&Key::Account(player_a.clone())).await
429 else {
430 panic!("Player A should have an account");
431 };
432 let player_a_creature = account_a.creature.as_ref().unwrap().clone();
433 let player_a_stats = account_a.stats.clone();
434 let player_a_health = player_a_creature.health();
435 account_a.battle = Some(key);
436 self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
437
438 let Some(Value::Account(mut account_b)) =
440 self.get(&Key::Account(player_b.clone())).await
441 else {
442 panic!("Player B should have an account");
443 };
444 let player_b_creature = account_b.creature.as_ref().unwrap().clone();
445 let player_b_stats = account_b.stats.clone();
446 let player_b_health = player_b_creature.health();
447 account_b.battle = Some(key);
448 self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
449
450 let expiry = self
452 .seed
453 .view
454 .checked_add(MOVE_EXPIRY)
455 .expect("view overflow");
456 let value = Value::Battle {
457 expiry,
458 round: 0,
459 player_a: (*player_a).clone(),
460 player_a_max_health: player_a_health,
461 player_a_health,
462 player_a_pending: None,
463 player_a_move_counts: [0; TOTAL_MOVES],
464 player_b: (*player_b).clone(),
465 player_b_max_health: player_b_health,
466 player_b_health,
467 player_b_pending: None,
468 player_b_move_counts: [0; TOTAL_MOVES],
469 };
470 self.insert(Key::Battle(key), value);
471 events.push(Event::Matched {
472 battle: key,
473 expiry,
474 player_a: (*player_a).clone(),
475 player_a_creature,
476 player_a_stats,
477 player_b: (*player_b).clone(),
478 player_b_creature,
479 player_b_stats,
480 });
481 }
482
483 let remainder = players.chunks_exact(2).remainder();
485 let mut new_players = BTreeSet::new();
486 if !remainder.is_empty() {
487 for player in remainder {
488 new_players.insert((*player).clone());
489 }
490 }
491
492 let lobby = Value::Lobby {
494 expiry: self
495 .seed
496 .view
497 .checked_add(LOBBY_EXPIRY)
498 .expect("view overflow"),
499 players: new_players,
500 };
501 self.insert(Key::Lobby, lobby);
502 } else {
503 let lobby = Value::Lobby { expiry, players };
505 self.insert(Key::Lobby, lobby);
506 }
507 }
508 Instruction::Move(encrypted_move) => {
509 let Some(battle) = account.battle else {
511 return events;
512 };
513
514 let Some(Value::Battle {
516 expiry,
517 round,
518 player_a,
519 player_a_max_health,
520 player_a_health,
521 mut player_a_pending,
522 player_a_move_counts,
523 player_b,
524 player_b_max_health,
525 player_b_health,
526 mut player_b_pending,
527 player_b_move_counts,
528 }) = self.get(&Key::Battle(battle)).await
529 else {
530 panic!("Battle should exist");
531 };
532
533 if expiry < self.seed.view {
535 return events;
536 }
537
538 if player_a == transaction.public && player_a_pending.is_none() {
540 player_a_pending = Some(encrypted_move.clone());
541 } else if player_b == transaction.public && player_b_pending.is_none() {
542 player_b_pending = Some(encrypted_move.clone());
543 } else {
544 return events;
545 }
546
547 events.push(Event::Locked {
549 battle,
550 round,
551 locker: transaction.public.clone(),
552 observer: if player_a == transaction.public {
553 player_b.clone()
554 } else {
555 player_a.clone()
556 },
557 ciphertext: encrypted_move.clone(),
558 });
559
560 let value = Value::Battle {
562 expiry,
563 round,
564 player_a,
565 player_a_max_health,
566 player_a_health,
567 player_a_pending,
568 player_a_move_counts,
569 player_b,
570 player_b_max_health,
571 player_b_health,
572 player_b_pending,
573 player_b_move_counts,
574 };
575 self.insert(Key::Battle(battle), value);
576 }
577 Instruction::Settle(signature) => {
578 let Some(battle) = account.battle else {
580 return events; };
582
583 let Some(Value::Battle {
585 expiry,
586 mut round,
587 player_a,
588 player_a_max_health,
589 mut player_a_health,
590 player_a_pending,
591 mut player_a_move_counts,
592 player_b,
593 player_b_max_health,
594 mut player_b_health,
595 player_b_pending,
596 mut player_b_move_counts,
597 }) = self.get(&Key::Battle(battle)).await
598 else {
599 panic!("Battle should exist");
600 };
601
602 if expiry > self.seed.view {
604 return events;
605 }
606
607 let seed = Seed::new(expiry, *signature);
609 if seed != self.seed {
610 match self.precomputations.get(&Task::Seed(seed.clone())) {
611 Some(TaskResult::Seed(result)) => {
612 if !result {
613 return events;
614 }
615 }
616 None => {
617 if !seed.verify(&self.namespace, &self.master) {
618 return events; }
620 }
621 _ => unreachable!(),
622 }
623 }
624
625 let mut player_a_move = if let Some(player_a_pending) = player_a_pending {
627 match self
629 .precomputations
630 .get(&Task::Decrypt(seed.clone(), player_a_pending.clone()))
631 {
632 Some(TaskResult::Decrypt(result)) => result[0],
633 None => {
634 let raw: [u8; 32] = decrypt::<MinSig>(signature, &player_a_pending)
635 .map(|block| block.as_ref().try_into().unwrap())
636 .unwrap_or_else(|| [0; 32]);
637 raw[0]
638 }
639 _ => unreachable!(),
640 }
641 } else {
642 0
644 };
645 if player_a_move >= TOTAL_MOVES as u8 {
646 player_a_move = 0;
647 }
648
649 let Some(Value::Account(account)) = self.get(&Key::Account(player_a.clone())).await
651 else {
652 panic!("Player A should have an account");
653 };
654 let player_a_creature = account.creature.as_ref().unwrap();
655
656 let player_a_limits = player_a_creature.get_move_usage_limits();
658 if player_a_move_counts[player_a_move as usize]
659 >= player_a_limits[player_a_move as usize]
660 {
661 player_a_move = 0;
662 }
663
664 let (player_a_defense, player_a_power) =
666 player_a_creature.action(player_a_move, self.seed.signature);
667
668 let mut player_b_move = if let Some(player_b_pending) = player_b_pending {
670 match self
672 .precomputations
673 .get(&Task::Decrypt(seed, player_b_pending.clone()))
674 {
675 Some(TaskResult::Decrypt(result)) => result[0],
676 None => {
677 let raw: [u8; 32] = decrypt::<MinSig>(signature, &player_b_pending)
678 .map(|block| block.as_ref().try_into().unwrap())
679 .unwrap_or_else(|| [0; 32]);
680 raw[0]
681 }
682 _ => unreachable!(),
683 }
684 } else {
685 0
687 };
688 if player_b_move >= TOTAL_MOVES as u8 {
689 player_b_move = 0;
690 }
691
692 let Some(Value::Account(account)) = self.get(&Key::Account(player_b.clone())).await
694 else {
695 panic!("Player B should have an account");
696 };
697 let player_b_creature = account.creature.as_ref().unwrap();
698
699 let player_b_limits = player_b_creature.get_move_usage_limits();
701 if player_b_move_counts[player_b_move as usize]
702 >= player_b_limits[player_b_move as usize]
703 {
704 player_b_move = 0;
705 }
706
707 let (player_b_defense, player_b_power) =
709 player_b_creature.action(player_b_move, self.seed.signature);
710
711 let mut player_a_effective_health = player_a_health as i16;
714 let mut player_b_effective_health = player_b_health as i16;
715
716 if player_a_defense && !player_b_defense {
717 player_a_health = player_a_health
719 .saturating_add(player_a_power)
720 .min(player_a_max_health);
721 player_a_effective_health = (player_a_health as i16) - (player_b_power as i16);
722 player_a_health = player_a_health.saturating_sub(player_b_power);
723 } else if !player_a_defense && player_b_defense {
724 player_b_health = player_b_health
726 .saturating_add(player_b_power)
727 .min(player_b_max_health);
728 player_b_effective_health = (player_b_health as i16) - (player_a_power as i16);
729 player_b_health = player_b_health.saturating_sub(player_a_power);
730 } else if player_a_defense && player_b_defense {
731 player_a_health = player_a_health
733 .saturating_add(player_a_power)
734 .min(player_a_max_health);
735 player_b_health = player_b_health
736 .saturating_add(player_b_power)
737 .min(player_b_max_health);
738 player_a_effective_health = player_a_health as i16;
739 player_b_effective_health = player_b_health as i16;
740 } else {
741 player_a_effective_health = (player_a_health as i16) - (player_b_power as i16);
743 player_b_effective_health = (player_b_health as i16) - (player_a_power as i16);
744 player_a_health = player_a_health.saturating_sub(player_b_power);
745 player_b_health = player_b_health.saturating_sub(player_a_power);
746 }
747 player_a_move_counts[player_a_move as usize] =
751 player_a_move_counts[player_a_move as usize].saturating_add(1);
752 player_b_move_counts[player_b_move as usize] =
753 player_b_move_counts[player_b_move as usize].saturating_add(1);
754
755 round += 1;
757
758 let next_expiry = self
760 .seed
761 .view
762 .checked_add(MOVE_EXPIRY)
763 .expect("view overflow");
764 events.push(Event::Moved {
765 battle,
766 round,
767 expiry: next_expiry,
768 player_a: player_a.clone(),
769 player_a_health,
770 player_a_move,
771 player_a_move_counts,
772 player_a_power,
773 player_b: player_b.clone(),
774 player_b_health,
775 player_b_move,
776 player_b_move_counts,
777 player_b_power,
778 });
779
780 if player_a_health == 0 && player_b_health > 0 {
782 self.delete(Key::Battle(battle));
784
785 let Some(Value::Account(mut account_a)) =
787 self.get(&Key::Account(player_a.clone())).await
788 else {
789 panic!("Player A should have an account");
790 };
791 let old_account_a_stats = account_a.stats.clone();
792 let Some(Value::Account(mut account_b)) =
793 self.get(&Key::Account(player_b.clone())).await
794 else {
795 panic!("Player B should have an account");
796 };
797 let old_account_b_stats = account_b.stats.clone();
798
799 account_a.stats.losses = account_a.stats.losses.saturating_add(1);
801 account_a.battle = None;
802
803 account_b.stats.wins = account_b.stats.wins.saturating_add(1);
805 account_b.battle = None;
806
807 let max_health_a = account_a.creature.as_ref().unwrap().health();
809 let max_health_b = account_b.creature.as_ref().unwrap().health();
810 let (new_elo_a, new_elo_b) = elo::update(
811 account_a.stats.elo,
812 player_a_effective_health,
813 max_health_a,
814 account_b.stats.elo,
815 player_b_effective_health,
816 max_health_b,
817 );
818 account_a.stats.elo = new_elo_a;
819 let new_account_a_stats = account_a.stats.clone();
820 account_b.stats.elo = new_elo_b;
821 let new_account_b_stats = account_b.stats.clone();
822 self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
823 self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
824
825 let mut leaderboard = match self.get(&Key::Leaderboard).await {
827 Some(Value::Leaderboard(lb)) => lb,
828 _ => Leaderboard::default(),
829 };
830 leaderboard.update(player_a.clone(), new_account_a_stats.clone());
831 leaderboard.update(player_b.clone(), new_account_b_stats.clone());
832 self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
833
834 events.push(Event::Settled {
836 battle,
837 round,
838 player_a,
839 player_a_old: old_account_a_stats,
840 player_a_new: new_account_a_stats,
841 player_b,
842 player_b_old: old_account_b_stats,
843 player_b_new: new_account_b_stats,
844 outcome: Outcome::PlayerB,
845 leaderboard,
846 });
847 } else if player_b_health == 0 && player_a_health > 0 {
848 self.delete(Key::Battle(battle));
850
851 let Some(Value::Account(mut account_a)) =
853 self.get(&Key::Account(player_a.clone())).await
854 else {
855 panic!("Player A should have an account");
856 };
857 let old_account_a_stats = account_a.stats.clone();
858 let Some(Value::Account(mut account_b)) =
859 self.get(&Key::Account(player_b.clone())).await
860 else {
861 panic!("Player B should have an account");
862 };
863 let old_account_b_stats = account_b.stats.clone();
864
865 account_b.stats.losses = account_b.stats.losses.saturating_add(1);
867 account_b.battle = None;
868
869 account_a.stats.wins = account_a.stats.wins.saturating_add(1);
871 account_a.battle = None;
872
873 let max_health_a = account_a.creature.as_ref().unwrap().health();
875 let max_health_b = account_b.creature.as_ref().unwrap().health();
876 let (new_elo_a, new_elo_b) = elo::update(
877 account_a.stats.elo,
878 player_a_effective_health,
879 max_health_a,
880 account_b.stats.elo,
881 player_b_effective_health,
882 max_health_b,
883 );
884 account_a.stats.elo = new_elo_a;
885 let new_account_a_stats = account_a.stats.clone();
886 account_b.stats.elo = new_elo_b;
887 let new_account_b_stats = account_b.stats.clone();
888 self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
889 self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
890
891 let mut leaderboard = match self.get(&Key::Leaderboard).await {
893 Some(Value::Leaderboard(lb)) => lb,
894 _ => Leaderboard::default(),
895 };
896 leaderboard.update(player_a.clone(), new_account_a_stats.clone());
897 leaderboard.update(player_b.clone(), new_account_b_stats.clone());
898 self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
899
900 events.push(Event::Settled {
902 battle,
903 round,
904 player_a,
905 player_a_old: old_account_a_stats,
906 player_a_new: new_account_a_stats,
907 player_b,
908 player_b_old: old_account_b_stats,
909 player_b_new: new_account_b_stats,
910 outcome: Outcome::PlayerA,
911 leaderboard,
912 });
913 } else if round >= MAX_BATTLE_ROUNDS
914 || (player_a_health == 0 && player_b_health == 0)
915 {
916 self.delete(Key::Battle(battle));
918
919 let Some(Value::Account(mut account_a)) =
921 self.get(&Key::Account(player_a.clone())).await
922 else {
923 panic!("Player A should have an account");
924 };
925 let old_account_a_stats = account_a.stats.clone();
926 let Some(Value::Account(mut account_b)) =
927 self.get(&Key::Account(player_b.clone())).await
928 else {
929 panic!("Player B should have an account");
930 };
931 let old_account_b_stats = account_b.stats.clone();
932
933 account_a.stats.draws = account_a.stats.draws.saturating_add(1);
935 account_a.battle = None;
936
937 account_b.stats.draws = account_b.stats.draws.saturating_add(1);
939 account_b.battle = None;
940
941 let max_health_a = account_a.creature.as_ref().unwrap().health();
943 let max_health_b = account_b.creature.as_ref().unwrap().health();
944 let (new_elo_a, new_elo_b) = elo::update(
945 account_a.stats.elo,
946 player_a_effective_health,
947 max_health_a,
948 account_b.stats.elo,
949 player_b_effective_health,
950 max_health_b,
951 );
952 account_a.stats.elo = new_elo_a;
953 let new_account_a_stats = account_a.stats.clone();
954 account_b.stats.elo = new_elo_b;
955 let new_account_b_stats = account_b.stats.clone();
956 self.insert(Key::Account(player_a.clone()), Value::Account(account_a));
957 self.insert(Key::Account(player_b.clone()), Value::Account(account_b));
958
959 let mut leaderboard = match self.get(&Key::Leaderboard).await {
961 Some(Value::Leaderboard(lb)) => lb,
962 _ => Leaderboard::default(),
963 };
964 leaderboard.update(player_a.clone(), new_account_a_stats.clone());
965 leaderboard.update(player_b.clone(), new_account_b_stats.clone());
966 self.insert(Key::Leaderboard, Value::Leaderboard(leaderboard.clone()));
967
968 events.push(Event::Settled {
970 battle,
971 round,
972 player_a,
973 player_a_old: old_account_a_stats,
974 player_a_new: new_account_a_stats,
975 player_b,
976 player_b_old: old_account_b_stats,
977 player_b_new: new_account_b_stats,
978 outcome: Outcome::Draw,
979 leaderboard,
980 });
981 } else {
982 self.insert(
984 Key::Battle(battle),
985 Value::Battle {
986 expiry: next_expiry,
987 round,
988 player_a,
989 player_a_max_health,
990 player_a_health,
991 player_a_pending: None,
992 player_a_move_counts,
993 player_b,
994 player_b_max_health,
995 player_b_health,
996 player_b_pending: None,
997 player_b_move_counts,
998 },
999 );
1000 }
1001 }
1002 }
1003
1004 events
1005 }
1006
1007 pub async fn execute(
1008 &mut self,
1009 #[cfg(feature = "parallel")] pool: ThreadPool,
1010 transactions: Vec<Transaction>,
1011 ) -> (Vec<Output>, BTreeMap<PublicKey, u64>) {
1012 let mut processed_nonces = BTreeMap::new();
1014 let mut seed_ops = HashSet::new();
1015 let mut decrypt_ops = HashSet::new();
1016 let mut valid_transactions = Vec::new();
1017 for tx in transactions {
1018 if !self.prepare(&tx).await {
1021 continue;
1022 }
1023
1024 processed_nonces.insert(tx.public.clone(), tx.nonce.saturating_add(1));
1026
1027 let ops = self.extract(&tx).await;
1029 for op in ops {
1030 match op {
1031 Task::Seed(_) => seed_ops.insert(op),
1032 Task::Decrypt(_, _) => decrypt_ops.insert(op),
1033 };
1034 }
1035 valid_transactions.push(tx);
1036 }
1037
1038 macro_rules! process_ops {
1040 ($iter:ident) => {{
1041 let mut results: HashMap<Task, TaskResult> = seed_ops
1043 .$iter()
1044 .map(|op| match op {
1045 Task::Seed(ref seed) => {
1046 if self.seed == *seed {
1047 return (op, TaskResult::Seed(true));
1048 }
1049 let result = seed.verify(&self.namespace, &self.master);
1050 (op, TaskResult::Seed(result))
1051 }
1052 _ => unreachable!(),
1053 })
1054 .collect();
1055
1056 let decrypt_results: HashMap<Task, TaskResult> = decrypt_ops
1058 .$iter()
1059 .flat_map(|op| match op {
1060 Task::Decrypt(ref seed, ref ciphertext) => {
1061 if !matches!(
1063 results.get(&Task::Seed(seed.clone())).unwrap(), TaskResult::Seed(true)
1065 ) {
1066 return None;
1067 }
1068
1069 let result = decrypt::<MinSig>(&seed.signature, ciphertext)
1071 .map(|block| block.as_ref().try_into().unwrap())
1072 .unwrap_or_else(|| [0; 32]);
1073 Some((op, TaskResult::Decrypt(result)))
1074 }
1075 _ => unreachable!(),
1076 })
1077 .collect();
1078
1079 results.extend(decrypt_results);
1081 results
1082 }};
1083 }
1084 #[cfg(feature = "parallel")]
1085 let precomputations = pool.install(|| process_ops!(into_par_iter));
1086 #[cfg(not(feature = "parallel"))]
1087 let precomputations = process_ops!(into_iter);
1088
1089 self.precomputations = precomputations;
1091
1092 let mut events = Vec::new();
1094 for tx in valid_transactions {
1095 events.extend(self.apply(&tx).await.into_iter().map(Output::Event));
1096 events.push(Output::Transaction(tx));
1097 }
1098
1099 (events, processed_nonces)
1100 }
1101
1102 pub fn commit(self) -> Vec<(Key, Status)> {
1103 self.pending.into_iter().collect()
1104 }
1105}
1106
1107impl<'a, S: State> State for Layer<'a, S> {
1108 async fn get(&self, key: &Key) -> Option<Value> {
1109 match self.pending.get(key) {
1110 Some(Status::Update(value)) => Some(value.clone()),
1111 Some(Status::Delete) => None,
1112 None => self.state.get(key).await,
1113 }
1114 }
1115
1116 async fn insert(&mut self, key: Key, value: Value) {
1117 self.pending.insert(key, Status::Update(value));
1118 }
1119
1120 async fn delete(&mut self, key: &Key) {
1121 self.pending.insert(key.clone(), Status::Delete);
1122 }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use super::*;
1128 use commonware_cryptography::bls12381::tle::{encrypt, Block, Ciphertext};
1129 use commonware_cryptography::{
1130 bls12381::primitives::{ops, variant::MinSig},
1131 ed25519, PrivateKeyExt, Signer,
1132 };
1133 use commonware_runtime::deterministic::Runner;
1134 use commonware_runtime::Runner as _;
1135 use rand::{rngs::StdRng, SeedableRng};
1136 use std::collections::HashMap;
1137
1138 const TEST_NAMESPACE: &[u8] = b"test-namespace";
1139
1140 struct MockState {
1141 data: HashMap<Key, Value>,
1142 }
1143
1144 impl MockState {
1145 fn new() -> Self {
1146 Self {
1147 data: HashMap::new(),
1148 }
1149 }
1150 }
1151
1152 impl State for MockState {
1153 async fn get(&self, key: &Key) -> Option<Value> {
1154 self.data.get(key).cloned()
1155 }
1156
1157 async fn insert(&mut self, key: Key, value: Value) {
1158 self.data.insert(key, value);
1159 }
1160
1161 async fn delete(&mut self, key: &Key) {
1162 self.data.remove(key);
1163 }
1164 }
1165
1166 fn create_network_keypair() -> (
1168 commonware_cryptography::bls12381::primitives::group::Private,
1169 <MinSig as commonware_cryptography::bls12381::primitives::variant::Variant>::Public,
1170 ) {
1171 let mut rng = StdRng::seed_from_u64(0);
1172 ops::keypair::<_, MinSig>(&mut rng)
1173 }
1174
1175 fn create_seed(
1176 network_secret: &commonware_cryptography::bls12381::primitives::group::Private,
1177 view: u64,
1178 ) -> Seed {
1179 use commonware_consensus::threshold_simplex::types::{seed_namespace, view_message};
1180 let seed_namespace = seed_namespace(TEST_NAMESPACE);
1181 let message = view_message(view);
1182 Seed::new(
1183 view,
1184 ops::sign_message::<MinSig>(network_secret, Some(&seed_namespace), &message),
1185 )
1186 }
1187
1188 fn create_test_move_ciphertext(
1189 master_public: <MinSig as commonware_cryptography::bls12381::primitives::variant::Variant>::Public,
1190 next_expiry: u64,
1191 move_data: u8,
1192 ) -> Ciphertext<MinSig> {
1193 use commonware_consensus::threshold_simplex::types::{seed_namespace, view_message};
1194
1195 let seed_namespace = seed_namespace(TEST_NAMESPACE);
1197 let view_msg = view_message(next_expiry);
1198
1199 let mut message = [0u8; 32];
1201 message[0] = move_data; let mut rng = StdRng::seed_from_u64(42); encrypt::<_, MinSig>(
1205 &mut rng,
1206 master_public,
1207 (Some(&seed_namespace), &view_msg),
1208 &Block::new(message),
1209 )
1210 }
1211
1212 #[allow(dead_code)]
1213 fn decrypt_test_move(
1214 network_secret: &commonware_cryptography::bls12381::primitives::group::Private,
1215 next_expiry: u64,
1216 ciphertext: &Ciphertext<MinSig>,
1217 ) -> Option<u8> {
1218 use commonware_cryptography::bls12381::tle::decrypt;
1219
1220 let seed = create_seed(network_secret, next_expiry);
1221 decrypt::<MinSig>(&seed.signature, ciphertext).map(|block| block.as_ref()[0])
1222 }
1223
1224 fn create_test_actor(seed: u64) -> (ed25519::PrivateKey, ed25519::PublicKey) {
1225 let private = ed25519::PrivateKey::from_seed(seed);
1226 let public = private.public_key();
1227 (private, public)
1228 }
1229
1230 #[test]
1231 fn test_invalid_nonce_dropped() {
1232 let executor = Runner::default();
1233 executor.start(|_| async move {
1234 let state = MockState::new();
1235 let (network_secret, master_public) = create_network_keypair();
1236 let seed = create_seed(&network_secret, 1);
1237 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1238
1239 let (signer, _) = create_test_actor(1);
1240
1241 let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1243 assert!(!layer.prepare(&tx).await);
1244
1245 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
1247 assert!(layer.prepare(&tx).await);
1248
1249 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
1252 assert!(!layer.prepare(&tx).await);
1253
1254 let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1256 assert!(layer.prepare(&tx).await);
1257
1258 let _ = layer.commit();
1260 });
1261 }
1262
1263 #[test]
1264 fn test_must_have_creature_before_battle() {
1265 let executor = Runner::default();
1266 executor.start(|_| async move {
1267 let state = MockState::new();
1268 let (network_secret, master_public) = create_network_keypair();
1269 let seed = create_seed(&network_secret, 1);
1270 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1271
1272 let (signer, _) = create_test_actor(1);
1273
1274 let tx = Transaction::sign(&signer, 0, Instruction::Match);
1276 assert!(layer.prepare(&tx).await);
1277
1278 let events = layer.apply(&tx).await;
1280 assert!(events.is_empty()); let tx = Transaction::sign(&signer, 1, Instruction::Generate);
1284 assert!(layer.prepare(&tx).await);
1285 let events = layer.apply(&tx).await;
1286 assert_eq!(events.len(), 1);
1287 assert!(matches!(events[0], Event::Generated { .. }));
1288
1289 let tx = Transaction::sign(&signer, 2, Instruction::Match);
1292 assert!(layer.prepare(&tx).await);
1293 let events = layer.apply(&tx).await;
1294 assert!(events.is_empty()); let _ = layer.commit();
1298 });
1299 }
1300
1301 #[test]
1302 fn test_cannot_enter_multiple_concurrent_battles() {
1303 let executor = Runner::default();
1304 executor.start(|_| async move {
1305 let mut state = MockState::new();
1306 let (network_secret, master_public) = create_network_keypair();
1307 let seed = create_seed(&network_secret, 1);
1308 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1309
1310 let (signer_a, _) = create_test_actor(1);
1311 let (signer_b, _) = create_test_actor(2);
1312
1313 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1315 assert!(layer.prepare(&tx).await);
1316 layer.apply(&tx).await;
1317
1318 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1319 assert!(layer.prepare(&tx).await);
1320 layer.apply(&tx).await;
1321
1322 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1324 assert!(layer.prepare(&tx).await);
1325 layer.apply(&tx).await;
1326
1327 let changes = layer.commit();
1329 state.apply(changes).await;
1330 let new_seed = create_seed(&network_secret, 102);
1331 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1332
1333 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1334 assert!(layer.prepare(&tx).await);
1335 let events = layer.apply(&tx).await;
1336 assert_eq!(events.len(), 1);
1337 assert!(matches!(events[0], Event::Matched { .. }));
1338
1339 let tx = Transaction::sign(&signer_a, 2, Instruction::Match);
1341 assert!(layer.prepare(&tx).await);
1342 let events = layer.apply(&tx).await;
1343 assert!(events.is_empty()); let _ = layer.commit();
1347 });
1348 }
1349
1350 #[test]
1351 fn test_can_only_swap_creatures_when_not_in_battle() {
1352 let executor = Runner::default();
1353 executor.start(|_| async move {
1354 let mut state = MockState::new();
1355 let (network_secret, master_public) = create_network_keypair();
1356 let seed = create_seed(&network_secret, 1);
1357 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1358
1359 let (signer_a, _) = create_test_actor(1);
1360 let (signer_b, _) = create_test_actor(2);
1361
1362 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1364 assert!(layer.prepare(&tx).await);
1365 layer.apply(&tx).await;
1366
1367 let tx = Transaction::sign(&signer_a, 1, Instruction::Generate);
1369 assert!(layer.prepare(&tx).await);
1370 let events = layer.apply(&tx).await;
1371 assert_eq!(events.len(), 1);
1372 assert!(matches!(events[0], Event::Generated { .. }));
1373
1374 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1376 assert!(layer.prepare(&tx).await);
1377 layer.apply(&tx).await;
1378
1379 let tx = Transaction::sign(&signer_a, 2, Instruction::Match);
1380 assert!(layer.prepare(&tx).await);
1381 layer.apply(&tx).await;
1382
1383 let changes = layer.commit();
1385 state.apply(changes).await;
1386 let new_seed = create_seed(&network_secret, 102);
1387 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1388
1389 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1390 assert!(layer.prepare(&tx).await);
1391 let events = layer.apply(&tx).await;
1392 assert_eq!(events.len(), 1);
1393 assert!(matches!(events[0], Event::Matched { .. }));
1394
1395 let tx = Transaction::sign(&signer_a, 3, Instruction::Generate);
1397 assert!(layer.prepare(&tx).await);
1398 let events = layer.apply(&tx).await;
1399 assert!(events.is_empty());
1400
1401 let _ = layer.commit();
1403 });
1404 }
1405
1406 #[test]
1407 fn test_cannot_send_multiple_moves_in_single_round() {
1408 let executor = Runner::default();
1409 executor.start(|_| async move {
1410 let mut state = MockState::new();
1411 let (network_secret, master_public) = create_network_keypair();
1412 let seed = create_seed(&network_secret, 1);
1413 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1414
1415 let (signer_a, _) = create_test_actor(1);
1416 let (signer_b, _) = create_test_actor(2);
1417
1418 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1420 assert!(layer.prepare(&tx).await);
1421 layer.apply(&tx).await;
1422
1423 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1424 assert!(layer.prepare(&tx).await);
1425 layer.apply(&tx).await;
1426
1427 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1428 assert!(layer.prepare(&tx).await);
1429 layer.apply(&tx).await;
1430
1431 let changes = layer.commit();
1433 state.apply(changes).await;
1434 let new_seed = create_seed(&network_secret, 102);
1435 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1436
1437 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1438 assert!(layer.prepare(&tx).await);
1439 let events = layer.apply(&tx).await;
1440 assert_eq!(events.len(), 1);
1441
1442 let battle_expiry = layer
1444 .view()
1445 .checked_add(MOVE_EXPIRY)
1446 .expect("view overflow");
1447 let move1 = create_test_move_ciphertext(master_public, battle_expiry, 1);
1448 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move1.clone()));
1449 assert!(layer.prepare(&tx).await);
1450 layer.apply(&tx).await;
1451
1452 let move2 = create_test_move_ciphertext(master_public, battle_expiry, 2);
1454 let tx = Transaction::sign(&signer_a, 3, Instruction::Move(move2));
1455 assert!(layer.prepare(&tx).await);
1456 let events = layer.apply(&tx).await;
1457 assert!(events.is_empty());
1458
1459 let _ = layer.commit();
1461 });
1462 }
1463
1464 #[test]
1465 fn test_one_player_can_win_if_other_doesnt_play() {
1466 let executor = Runner::default();
1467 executor.start(|_| async move {
1468 let mut state = MockState::new();
1469 let (network_secret, master_public) = create_network_keypair();
1470 let seed = create_seed(&network_secret, 1);
1471 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1472
1473 let (signer_a, _) = create_test_actor(1);
1474 let (signer_b, _) = create_test_actor(2);
1475
1476 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1478 assert!(layer.prepare(&tx).await);
1479 layer.apply(&tx).await;
1480
1481 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1482 assert!(layer.prepare(&tx).await);
1483 layer.apply(&tx).await;
1484
1485 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1486 assert!(layer.prepare(&tx).await);
1487 layer.apply(&tx).await;
1488
1489 let changes = layer.commit();
1491 state.apply(changes).await;
1492 let new_seed = create_seed(&network_secret, 102);
1493 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1494
1495 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1496 assert!(layer.prepare(&tx).await);
1497 let events = layer.apply(&tx).await;
1498 assert_eq!(events.len(), 1);
1499
1500 let battle_expiry = layer
1502 .view()
1503 .checked_add(MOVE_EXPIRY)
1504 .expect("view overflow");
1505
1506 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 2);
1508 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
1509 assert!(layer.prepare(&tx).await);
1510 layer.apply(&tx).await;
1511
1512 let changes = layer.commit();
1514 state.apply(changes).await;
1515 let new_seed = create_seed(&network_secret, battle_expiry + 1);
1516 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1517
1518 let signature = create_seed(&network_secret, battle_expiry);
1519 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
1520 assert!(layer.prepare(&tx).await);
1521 let events = layer.apply(&tx).await;
1522
1523 assert!(events.iter().any(|e| matches!(e, Event::Moved { .. })));
1525
1526 let _ = layer.commit();
1531 });
1532 }
1533
1534 #[test]
1535 fn test_scores_update_correctly_on_game_over() {
1536 let executor = Runner::default();
1537 executor.start(|_| async move {
1538 let mut state = MockState::new();
1539 let (network_secret, master_public) = create_network_keypair();
1540 let seed = create_seed(&network_secret, 1);
1541 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1542
1543 let (signer_a, actor_a) = create_test_actor(1);
1544 let (signer_b, actor_b) = create_test_actor(2);
1545
1546 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1548 assert!(layer.prepare(&tx).await);
1549 layer.apply(&tx).await;
1550
1551 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1552 assert!(layer.prepare(&tx).await);
1553 layer.apply(&tx).await;
1554
1555 let account_a_initial = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1557 let account_b_initial = layer.get(&Key::Account(actor_b.clone())).await.unwrap();
1558
1559 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1560 assert!(layer.prepare(&tx).await);
1561 layer.apply(&tx).await;
1562
1563 let changes = layer.commit();
1565 state.apply(changes).await;
1566 let new_seed = create_seed(&network_secret, 102);
1567 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1568
1569 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1570 assert!(layer.prepare(&tx).await);
1571 let events = layer.apply(&tx).await;
1572 assert_eq!(events.len(), 1);
1573
1574 let _battle_digest = if let Event::Matched { battle, .. } = &events[0] {
1575 *battle
1576 } else {
1577 panic!("Expected matched event");
1578 };
1579
1580 let mut round = 0;
1582 loop {
1583 let battle_expiry = layer
1585 .view()
1586 .checked_add(MOVE_EXPIRY)
1587 .expect("view overflow");
1588
1589 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 3);
1591 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
1592
1593 let tx = Transaction::sign(&signer_a, 2 + round * 2, Instruction::Move(move_a));
1594 assert!(layer.prepare(&tx).await);
1595 layer.apply(&tx).await;
1596
1597 let tx = Transaction::sign(&signer_b, 2 + round, Instruction::Move(move_b));
1598 assert!(layer.prepare(&tx).await);
1599 layer.apply(&tx).await;
1600
1601 let changes = layer.commit();
1603 state.apply(changes).await;
1604 let new_seed = create_seed(&network_secret, battle_expiry + 1);
1605 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1606
1607 let signature = create_seed(&network_secret, battle_expiry);
1608 let tx = Transaction::sign(
1609 &signer_a,
1610 3 + round * 2,
1611 Instruction::Settle(signature.signature),
1612 );
1613 assert!(layer.prepare(&tx).await);
1614 let events = layer.apply(&tx).await;
1615
1616 if let Some(settled_event) =
1618 events.iter().find(|e| matches!(e, Event::Settled { .. }))
1619 {
1620 let account_a_final = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1622 let account_b_final = layer.get(&Key::Account(actor_b.clone())).await.unwrap();
1623
1624 if let (
1625 Value::Account(mut acc_a_init),
1626 Value::Account(mut acc_a_final),
1627 Value::Account(mut acc_b_init),
1628 Value::Account(mut acc_b_final),
1629 ) = (
1630 account_a_initial.clone(),
1631 account_a_final,
1632 account_b_initial.clone(),
1633 account_b_final,
1634 ) {
1635 assert!(
1637 acc_a_final.stats.wins > acc_a_init.stats.wins
1638 || acc_a_final.stats.losses > acc_a_init.stats.losses
1639 || acc_a_final.stats.draws > acc_a_init.stats.draws
1640 );
1641 assert!(
1642 acc_b_final.stats.wins > acc_b_init.stats.wins
1643 || acc_b_final.stats.losses > acc_b_init.stats.losses
1644 || acc_b_final.stats.draws > acc_b_init.stats.draws
1645 );
1646
1647 assert!(
1649 acc_a_final.stats.elo != acc_a_init.stats.elo
1650 || acc_b_final.stats.elo != acc_b_init.stats.elo
1651 );
1652
1653 assert!(acc_a_final.battle.is_none());
1655 assert!(acc_b_final.battle.is_none());
1656
1657 if let Event::Settled {
1659 player_a,
1660 player_a_old,
1661 player_a_new,
1662 player_b_old,
1663 player_b_new,
1664 ..
1665 } = settled_event
1666 {
1667 if player_a != &signer_a.public_key() {
1669 let acc_temp_init = acc_a_init;
1670 let acc_temp_final = acc_a_final;
1671 acc_a_init = acc_b_init;
1672 acc_a_final = acc_b_final;
1673 acc_b_init = acc_temp_init;
1674 acc_b_final = acc_temp_final;
1675 }
1676
1677 assert_eq!(
1679 player_a_old.elo, acc_a_init.stats.elo,
1680 "Player A old Elo should match initial Elo"
1681 );
1682 assert_eq!(
1683 player_b_old.elo, acc_b_init.stats.elo,
1684 "Player B old Elo should match initial Elo"
1685 );
1686
1687 assert_eq!(
1689 player_a_new.elo, acc_a_final.stats.elo,
1690 "Player A new Elo should match final Elo"
1691 );
1692 assert_eq!(
1693 player_b_new.elo, acc_b_final.stats.elo,
1694 "Player B new Elo should match final Elo"
1695 );
1696
1697 assert_ne!(
1699 player_a_old.elo, player_a_new.elo,
1700 "Player A Elo should have changed"
1701 );
1702 assert_ne!(
1703 player_b_old.elo, player_b_new.elo,
1704 "Player B Elo should have changed"
1705 );
1706 }
1707 }
1708 break;
1709 }
1710
1711 round += 1;
1712 if round > 10 {
1713 panic!("Game should have ended by now");
1714 }
1715 }
1716
1717 let _ = layer.commit();
1719 });
1720 }
1721
1722 #[test]
1723 fn test_invalid_signature_does_nothing() {
1724 let executor = Runner::default();
1725 executor.start(|_| async move {
1726 let mut state = MockState::new();
1727 let (network_secret, master_public) = create_network_keypair();
1728 let seed = create_seed(&network_secret, 1);
1729 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1730
1731 let (signer_a, actor_a) = create_test_actor(1);
1732 let (signer_b, _) = create_test_actor(2);
1733
1734 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1736 assert!(layer.prepare(&tx).await);
1737 layer.apply(&tx).await;
1738
1739 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1740 assert!(layer.prepare(&tx).await);
1741 layer.apply(&tx).await;
1742
1743 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1744 assert!(layer.prepare(&tx).await);
1745 layer.apply(&tx).await;
1746
1747 let changes = layer.commit();
1749 state.apply(changes).await;
1750 let new_seed = create_seed(&network_secret, 102);
1751 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1752
1753 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1754 assert!(layer.prepare(&tx).await);
1755 let events = layer.apply(&tx).await;
1756 assert_eq!(events.len(), 1);
1757
1758 let battle_expiry = layer
1760 .view()
1761 .checked_add(MOVE_EXPIRY)
1762 .expect("view overflow");
1763
1764 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
1766 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 2);
1767
1768 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
1769 assert!(layer.prepare(&tx).await);
1770 layer.apply(&tx).await;
1771
1772 let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
1773 assert!(layer.prepare(&tx).await);
1774 layer.apply(&tx).await;
1775
1776 let changes = layer.commit();
1778 state.apply(changes).await;
1779 let new_seed = create_seed(&network_secret, battle_expiry + 1);
1780 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1781
1782 let wrong_signature =
1783 ops::sign_message::<MinSig>(&network_secret, Some(b"test"), b"wrong");
1784
1785 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(wrong_signature));
1786 assert!(layer.prepare(&tx).await);
1787 let events = layer.apply(&tx).await;
1788 assert!(events.is_empty()); let battle_key = layer.get(&Key::Account(actor_a.clone())).await.unwrap();
1792 if let Value::Account(account) = battle_key {
1793 assert!(account.battle.is_some());
1794 }
1795
1796 let _ = layer.commit();
1798 });
1799 }
1800
1801 #[test]
1802 fn test_decryption_failure_defaults_to_no_move() {
1803 let executor = Runner::default();
1804 executor.start(|_| async move {
1805 let mut state = MockState::new();
1806 let (network_secret, master_public) = create_network_keypair();
1807 let seed = create_seed(&network_secret, 1);
1808 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1809
1810 let (signer_a, _) = create_test_actor(1);
1811 let (signer_b, _) = create_test_actor(2);
1812
1813 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1815 assert!(layer.prepare(&tx).await);
1816 layer.apply(&tx).await;
1817
1818 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1819 assert!(layer.prepare(&tx).await);
1820 layer.apply(&tx).await;
1821
1822 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1823 assert!(layer.prepare(&tx).await);
1824 layer.apply(&tx).await;
1825
1826 let changes = layer.commit();
1828 state.apply(changes).await;
1829 let new_seed = create_seed(&network_secret, 102);
1830 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1831
1832 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1833 assert!(layer.prepare(&tx).await);
1834 let events = layer.apply(&tx).await;
1835 assert_eq!(events.len(), 1);
1836
1837 let (_, actual_player_a, _) = match &events[0] {
1839 Event::Matched {
1840 battle,
1841 expiry: _,
1842 player_a,
1843 player_a_creature: _,
1844 player_a_stats: _,
1845 player_b,
1846 player_b_creature: _,
1847 player_b_stats: _,
1848 } => (*battle, player_a.clone(), player_b.clone()),
1849 _ => panic!("Expected Matched event"),
1850 };
1851
1852 let battle_expiry = layer
1854 .view()
1855 .checked_add(MOVE_EXPIRY)
1856 .expect("view overflow");
1857
1858 let (bad_move_signer, bad_move_nonce, good_move_signer, good_move_nonce) =
1861 if actual_player_a == signer_a.public_key() {
1862 (&signer_a, 2, &signer_b, 2)
1864 } else {
1865 (&signer_b, 2, &signer_a, 2)
1867 };
1868
1869 let bad_move = create_test_move_ciphertext(master_public, 9999, 3); let good_move = create_test_move_ciphertext(master_public, battle_expiry, 2);
1872
1873 let tx =
1874 Transaction::sign(bad_move_signer, bad_move_nonce, Instruction::Move(bad_move));
1875 assert!(layer.prepare(&tx).await);
1876 layer.apply(&tx).await;
1877
1878 let tx = Transaction::sign(
1879 good_move_signer,
1880 good_move_nonce,
1881 Instruction::Move(good_move),
1882 );
1883 assert!(layer.prepare(&tx).await);
1884 layer.apply(&tx).await;
1885
1886 let changes = layer.commit();
1888 state.apply(changes).await;
1889 let new_seed = create_seed(&network_secret, battle_expiry + 1);
1890 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1891
1892 let signature = create_seed(&network_secret, battle_expiry);
1893
1894 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
1895 assert!(layer.prepare(&tx).await);
1896 let events = layer.apply(&tx).await;
1897
1898 let move_event = events
1900 .iter()
1901 .find(|e| matches!(e, Event::Moved { .. }))
1902 .unwrap();
1903
1904 if let Event::Moved {
1905 player_a_move,
1906 player_b_move,
1907 ..
1908 } = move_event
1909 {
1910 assert_eq!(*player_a_move, 0);
1912 assert_eq!(*player_b_move, 2);
1914 }
1915
1916 let _ = layer.commit();
1918 });
1919 }
1920
1921 #[test]
1922 fn test_move_usage_limits_respected() {
1923 let executor = Runner::default();
1924 executor.start(|_| async move {
1925 let mut state = MockState::new();
1926 let (network_secret, master_public) = create_network_keypair();
1927 let seed = create_seed(&network_secret, 1);
1928 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1929 let (signer_a, _) = create_test_actor(1);
1930 let (signer_b, _) = create_test_actor(2);
1931
1932 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
1934 assert!(layer.prepare(&tx).await);
1935 layer.apply(&tx).await;
1936 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
1937 assert!(layer.prepare(&tx).await);
1938 layer.apply(&tx).await;
1939 let changes = layer.commit();
1940 state.apply(changes).await;
1941
1942 let seed = create_seed(&network_secret, 1);
1944 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
1945 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
1946 assert!(layer.prepare(&tx).await);
1947 layer.apply(&tx).await;
1948 let changes = layer.commit();
1949 state.apply(changes).await;
1950
1951 let new_seed = create_seed(&network_secret, 102);
1953 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
1954 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
1955 assert!(layer.prepare(&tx).await);
1956 let events = layer.apply(&tx).await;
1957 let Some(Event::Matched { player_a, .. }) = events.first() else {
1958 panic!("Expected Matched event");
1959 };
1960 let mut nonce_a = 2;
1961 let mut nonce_b = 2;
1962
1963 let is_signer_a_player_a = signer_a.public_key() == *player_a;
1965
1966 let Some(Value::Account(account_player_a)) =
1969 layer.get(&Key::Account(player_a.clone())).await
1970 else {
1971 panic!("Player A account should exist");
1972 };
1973 let creature_player_a = account_player_a.creature.as_ref().unwrap();
1974 let move_limits = creature_player_a.get_move_usage_limits();
1975
1976 let mut strongest_move = 0;
1978 let mut min_limit = u8::MAX;
1979 for (i, &limit) in move_limits.iter().enumerate().skip(1) {
1980 if limit < min_limit {
1981 min_limit = limit;
1982 strongest_move = i as u8;
1983 }
1984 }
1985
1986 let battle_key = account_player_a.battle.unwrap();
1988
1989 for _ in 0..min_limit {
1991 let battle_expiry = layer
1992 .view()
1993 .checked_add(MOVE_EXPIRY)
1994 .expect("view overflow");
1995
1996 let (move_signer_a, move_signer_b) = if is_signer_a_player_a {
1998 (
2001 create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2002 create_test_move_ciphertext(master_public, battle_expiry, 1),
2003 )
2004 } else {
2005 (
2008 create_test_move_ciphertext(master_public, battle_expiry, 1),
2009 create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2010 )
2011 };
2012
2013 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_signer_a));
2014 assert!(layer.prepare(&tx).await);
2015 let events = layer.apply(&tx).await;
2016 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2017 nonce_a += 1;
2018 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_signer_b));
2019 assert!(layer.prepare(&tx).await);
2020 let events = layer.apply(&tx).await;
2021 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2022 nonce_b += 1;
2023
2024 let changes = layer.commit();
2026 state.apply(changes).await;
2027 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2028 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2029 let signature = create_seed(&network_secret, battle_expiry);
2030 let tx =
2031 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2032 assert!(layer.prepare(&tx).await);
2033 let events = layer.apply(&tx).await;
2034 nonce_a += 1;
2035
2036 let move_event = events
2038 .iter()
2039 .find(|e| matches!(e, Event::Moved { .. }))
2040 .unwrap();
2041 if let Event::Moved {
2042 player_a_move,
2043 player_b_move,
2044 ..
2045 } = move_event
2046 {
2047 assert_eq!(*player_a_move, strongest_move);
2049 assert_eq!(*player_b_move, 1);
2050 }
2051
2052 if layer.get(&Key::Battle(battle_key)).await.is_none() {
2054 break;
2055 }
2056 }
2057
2058 if let Some(Value::Battle { .. }) = layer.get(&Key::Battle(battle_key)).await {
2060 let battle_expiry = layer
2061 .view()
2062 .checked_add(MOVE_EXPIRY)
2063 .expect("view overflow");
2064
2065 let (move_signer_a, move_signer_b) = if is_signer_a_player_a {
2067 (
2070 create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2071 create_test_move_ciphertext(master_public, battle_expiry, 1),
2072 )
2073 } else {
2074 (
2077 create_test_move_ciphertext(master_public, battle_expiry, 1),
2078 create_test_move_ciphertext(master_public, battle_expiry, strongest_move),
2079 )
2080 };
2081
2082 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_signer_a));
2083 assert!(layer.prepare(&tx).await);
2084 layer.apply(&tx).await;
2085 nonce_a += 1;
2086 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_signer_b));
2087 assert!(layer.prepare(&tx).await);
2088 layer.apply(&tx).await;
2089
2090 let changes = layer.commit();
2092 state.apply(changes).await;
2093 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2094 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2095 let signature = create_seed(&network_secret, battle_expiry);
2096 let tx =
2097 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2098 assert!(layer.prepare(&tx).await);
2099 let events = layer.apply(&tx).await;
2100
2101 let move_event = events
2103 .iter()
2104 .find(|e| matches!(e, Event::Moved { .. }))
2105 .unwrap();
2106 if let Event::Moved {
2107 player_a_move,
2108 player_b_move,
2109 ..
2110 } = move_event
2111 {
2112 assert_eq!(*player_a_move, 0);
2114 assert_eq!(*player_b_move, 1);
2115 }
2116 }
2117
2118 let _ = layer.commit();
2119 });
2120 }
2121
2122 #[test]
2123 fn test_move_usage_counts_persist_across_rounds() {
2124 let executor = Runner::default();
2125 executor.start(|_| async move {
2126 let mut state = MockState::new();
2127 let (network_secret, master_public) = create_network_keypair();
2128 let seed = create_seed(&network_secret, 1);
2129 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2130 let (signer_a, actor_a) = create_test_actor(1);
2131 let (signer_b, _) = create_test_actor(2);
2132
2133 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2135 assert!(layer.prepare(&tx).await);
2136 layer.apply(&tx).await;
2137 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2138 assert!(layer.prepare(&tx).await);
2139 layer.apply(&tx).await;
2140 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2141 assert!(layer.prepare(&tx).await);
2142 layer.apply(&tx).await;
2143
2144 let changes = layer.commit();
2145 state.apply(changes).await;
2146 let new_seed = create_seed(&network_secret, 102);
2147 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2148 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2149 assert!(layer.prepare(&tx).await);
2150 let events = layer.apply(&tx).await;
2151 let Some(Event::Matched { player_a, .. }) = events.first() else {
2152 panic!("Expected Matched event");
2153 };
2154
2155 let Some(Value::Account(account)) = layer.get(&Key::Account(actor_a.clone())).await
2156 else {
2157 panic!("Account should exist");
2158 };
2159 let battle_key = account.battle.unwrap();
2160
2161 let battle_expiry = layer
2163 .view()
2164 .checked_add(MOVE_EXPIRY)
2165 .expect("view overflow");
2166 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 3);
2167 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
2168
2169 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2170 assert!(layer.prepare(&tx).await);
2171 let events = layer.apply(&tx).await;
2172 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2173 let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2174 assert!(layer.prepare(&tx).await);
2175 let events = layer.apply(&tx).await;
2176 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2177
2178 let changes = layer.commit();
2179 state.apply(changes).await;
2180 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2181 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2182 let signature = create_seed(&network_secret, battle_expiry);
2183 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2184 assert!(layer.prepare(&tx).await);
2185 let events = layer.apply(&tx).await;
2186
2187 let battle_ended = events.iter().any(|e| matches!(e, Event::Settled { .. }));
2189 if battle_ended {
2190 let _ = layer.commit();
2192 return;
2193 }
2194
2195 let Some(Value::Battle {
2197 player_a_move_counts,
2198 player_b_move_counts,
2199 ..
2200 }) = layer.get(&Key::Battle(battle_key)).await
2201 else {
2202 panic!("Battle should exist");
2203 };
2204 if signer_a.public_key() == *player_a {
2205 assert_eq!(
2206 player_a_move_counts[3], 1,
2207 "Move 2 should have been used once"
2208 );
2209 } else {
2210 assert_eq!(
2211 player_b_move_counts[3], 1,
2212 "Move 2 should have been used once"
2213 );
2214 }
2215
2216 let battle_expiry = layer
2218 .view()
2219 .checked_add(MOVE_EXPIRY)
2220 .expect("view overflow");
2221 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 4);
2222 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
2223
2224 let tx = Transaction::sign(&signer_a, 4, Instruction::Move(move_a));
2225 assert!(layer.prepare(&tx).await);
2226 layer.apply(&tx).await;
2227 let tx = Transaction::sign(&signer_b, 3, Instruction::Move(move_b));
2228 assert!(layer.prepare(&tx).await);
2229 layer.apply(&tx).await;
2230
2231 let changes = layer.commit();
2232 state.apply(changes).await;
2233 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2234 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2235 let signature = create_seed(&network_secret, battle_expiry);
2236 let tx = Transaction::sign(&signer_a, 5, Instruction::Settle(signature.signature));
2237 assert!(layer.prepare(&tx).await);
2238 layer.apply(&tx).await;
2239
2240 if let Some(Value::Battle {
2242 player_a_move_counts,
2243 player_b_move_counts,
2244 ..
2245 }) = layer.get(&Key::Battle(battle_key)).await
2246 {
2247 if signer_a.public_key() == *player_a {
2248 assert_eq!(player_a_move_counts[3], 1, "Move 2 count should persist");
2249 assert_eq!(
2250 player_a_move_counts[4], 1,
2251 "Move 3 should have been used once"
2252 );
2253 } else {
2254 assert_eq!(player_b_move_counts[3], 1, "Move 2 count should persist");
2255 assert_eq!(
2256 player_b_move_counts[4], 1,
2257 "Move 3 should have been used once"
2258 );
2259 }
2260 }
2261
2262 let _ = layer.commit();
2263 });
2264 }
2265
2266 #[test]
2267 fn test_creature_strength_method() {
2268 let executor = Runner::default();
2269 executor.start(|_| async move {
2270 let (network_secret, _) = create_network_keypair();
2271 let (_, actor) = create_test_actor(1);
2272 let seed = create_seed(&network_secret, 1);
2273 let creature = Creature::new(actor, 0, seed.signature);
2274
2275 assert_eq!(creature.health(), creature.traits[0]);
2277 });
2278 }
2279
2280 #[test]
2281 fn test_creature_get_move_strengths() {
2282 let executor = Runner::default();
2283 executor.start(|_| async move {
2284 let (network_secret, _) = create_network_keypair();
2285 let (_, actor) = create_test_actor(1);
2286 let seed = create_seed(&network_secret, 1);
2287 let creature = Creature::new(actor, 0, seed.signature);
2288
2289 let move_strengths = creature.get_move_strengths();
2290
2291 assert_eq!(move_strengths[0], 0); assert_eq!(move_strengths[1], creature.traits[1]); assert_eq!(move_strengths[2], creature.traits[2]); assert_eq!(move_strengths[3], creature.traits[3]); assert_eq!(move_strengths[4], creature.traits[4]); });
2298 }
2299
2300 #[test]
2301 fn test_creature_actions() {
2302 let executor = Runner::default();
2303 executor.start(|_| async move {
2304 let (network_secret, _) = create_network_keypair();
2305 let (_, actor) = create_test_actor(1);
2306 let seed = create_seed(&network_secret, 1);
2307 let creature = Creature::new(actor, 0, seed.signature);
2308
2309 assert_eq!(creature.action(0, seed.signature), (false, 0));
2310 assert_eq!(
2311 creature.action(TOTAL_MOVES as u8, seed.signature),
2312 (false, 0)
2313 );
2314 assert_eq!(creature.action(u8::MAX, seed.signature), (false, 0));
2315 });
2316 }
2317
2318 #[test]
2319 fn test_creature_action_minimum_effectiveness() {
2320 let executor = Runner::default();
2321 executor.start(|_| async move {
2322 let (network_secret, _) = create_network_keypair();
2323 let (_, actor) = create_test_actor(1);
2324 let seed = create_seed(&network_secret, 1);
2325 let creature = Creature::new(actor, 0, seed.signature);
2326
2327 for i in 0..100 {
2329 let (sk, _) = create_network_keypair();
2331 let test_seed = ops::sign_message::<MinSig>(&sk, Some(b"test"), &[i; 32]);
2332
2333 for move_idx in 1..TOTAL_MOVES as u8 {
2335 let (is_defense, power) = creature.action(move_idx, test_seed);
2336 let max_power = creature.traits[move_idx as usize];
2337 let min_expected = max_power / 2;
2338
2339 assert!(power >= min_expected);
2341
2342 assert!(power <= max_power);
2344
2345 assert_eq!(is_defense, move_idx == 1);
2347 }
2348 }
2349 });
2350 }
2351
2352 #[test]
2353 fn test_generate_multiple_times() {
2354 let executor = Runner::default();
2355 executor.start(|_| async move {
2356 let state = MockState::new();
2357 let (network_secret, master_public) = create_network_keypair();
2358 let seed = create_seed(&network_secret, 1);
2359 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2360 let (signer, _) = create_test_actor(1);
2361
2362 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2364 assert!(layer.prepare(&tx).await);
2365 let events = layer.apply(&tx).await;
2366 assert_eq!(events.len(), 1);
2367 assert!(matches!(events[0], Event::Generated { .. }));
2368
2369 let tx = Transaction::sign(&signer, 1, Instruction::Generate);
2371 assert!(layer.prepare(&tx).await);
2372 let events = layer.apply(&tx).await;
2373 assert_eq!(events.len(), 1);
2374 assert!(matches!(events[0], Event::Generated { .. }));
2375
2376 let _ = layer.commit();
2377 });
2378 }
2379
2380 #[test]
2381 fn test_match_with_empty_lobby() {
2382 let executor = Runner::default();
2383 executor.start(|_| async move {
2384 let state = MockState::new();
2385 let (network_secret, master_public) = create_network_keypair();
2386 let seed = create_seed(&network_secret, 1);
2387 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2388 let (signer, actor) = create_test_actor(1);
2389
2390 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2392 assert!(layer.prepare(&tx).await);
2393 layer.apply(&tx).await;
2394
2395 let tx = Transaction::sign(&signer, 1, Instruction::Match);
2397 assert!(layer.prepare(&tx).await);
2398 let events = layer.apply(&tx).await;
2399
2400 assert_eq!(events.len(), 0); if let Some(Value::Lobby { players, .. }) = layer.get(&Key::Lobby).await {
2405 assert!(players.contains(&actor));
2406 } else {
2407 panic!("Lobby should exist");
2408 }
2409
2410 let _ = layer.commit();
2411 });
2412 }
2413
2414 #[test]
2415 fn test_move_with_no_battle() {
2416 let executor = Runner::default();
2417 executor.start(|_| async move {
2418 let state = MockState::new();
2419 let (network_secret, master_public) = create_network_keypair();
2420 let seed = create_seed(&network_secret, 1);
2421 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2422 let (signer, _) = create_test_actor(1);
2423
2424 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2426 assert!(layer.prepare(&tx).await);
2427 layer.apply(&tx).await;
2428
2429 let encrypted_move = create_test_move_ciphertext(master_public, 100, 1);
2431 let tx = Transaction::sign(&signer, 1, Instruction::Move(encrypted_move));
2432 assert!(layer.prepare(&tx).await);
2433 let events = layer.apply(&tx).await;
2434
2435 assert_eq!(events.len(), 0);
2437
2438 let _ = layer.commit();
2439 });
2440 }
2441
2442 #[test]
2443 fn test_settle_with_no_battle() {
2444 let executor = Runner::default();
2445 executor.start(|_| async move {
2446 let state = MockState::new();
2447 let (network_secret, master_public) = create_network_keypair();
2448 let seed = create_seed(&network_secret, 1);
2449 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2450 let (signer, _) = create_test_actor(1);
2451
2452 let tx = Transaction::sign(&signer, 0, Instruction::Generate);
2454 assert!(layer.prepare(&tx).await);
2455 layer.apply(&tx).await;
2456
2457 let signature = create_seed(&network_secret, 100);
2459 let tx = Transaction::sign(&signer, 1, Instruction::Settle(signature.signature));
2460 assert!(layer.prepare(&tx).await);
2461 let events = layer.apply(&tx).await;
2462
2463 assert_eq!(events.len(), 0);
2465
2466 let _ = layer.commit();
2467 });
2468 }
2469
2470 #[test]
2471 fn test_move_when_turn_expired() {
2472 let executor = Runner::default();
2473 executor.start(|_| async move {
2474 let mut state = MockState::new();
2475 let (network_secret, master_public) = create_network_keypair();
2476 let seed = create_seed(&network_secret, 1);
2477 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2478 let (signer_a, _) = create_test_actor(1);
2479 let (signer_b, _) = create_test_actor(2);
2480
2481 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2483 assert!(layer.prepare(&tx).await);
2484 layer.apply(&tx).await;
2485 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2486 assert!(layer.prepare(&tx).await);
2487 layer.apply(&tx).await;
2488 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2489 assert!(layer.prepare(&tx).await);
2490 layer.apply(&tx).await;
2491
2492 let changes = layer.commit();
2493 state.apply(changes).await;
2494 let new_seed = create_seed(&network_secret, 102);
2495 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2496 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2497 assert!(layer.prepare(&tx).await);
2498 layer.apply(&tx).await;
2499
2500 let changes = layer.commit();
2502 state.apply(changes).await;
2503 let new_seed = create_seed(&network_secret, 202);
2504 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed); let encrypted_move = create_test_move_ciphertext(master_public, 102 + MOVE_EXPIRY, 1);
2508 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(encrypted_move));
2509 assert!(layer.prepare(&tx).await);
2510 let events = layer.apply(&tx).await;
2511
2512 assert_eq!(events.len(), 0);
2514
2515 let _ = layer.commit();
2516 });
2517 }
2518
2519 #[test]
2520 fn test_settle_before_turn_expired() {
2521 let executor = Runner::default();
2522 executor.start(|_| async move {
2523 let mut state = MockState::new();
2524 let (network_secret, master_public) = create_network_keypair();
2525 let seed = create_seed(&network_secret, 1);
2526 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2527 let (signer_a, _) = create_test_actor(1);
2528 let (signer_b, _) = create_test_actor(2);
2529
2530 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2532 assert!(layer.prepare(&tx).await);
2533 layer.apply(&tx).await;
2534 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2535 assert!(layer.prepare(&tx).await);
2536 layer.apply(&tx).await;
2537 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2538 assert!(layer.prepare(&tx).await);
2539 layer.apply(&tx).await;
2540
2541 let changes = layer.commit();
2542 state.apply(changes).await;
2543 let new_seed = create_seed(&network_secret, 102);
2544 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2545 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2546 assert!(layer.prepare(&tx).await);
2547 layer.apply(&tx).await;
2548
2549 let battle_expiry = layer
2550 .view()
2551 .checked_add(MOVE_EXPIRY)
2552 .expect("view overflow");
2553
2554 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
2556 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 2);
2557 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2558 assert!(layer.prepare(&tx).await);
2559 layer.apply(&tx).await;
2560 let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2561 assert!(layer.prepare(&tx).await);
2562 layer.apply(&tx).await;
2563
2564 let signature = create_seed(&network_secret, battle_expiry);
2566 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2567 assert!(layer.prepare(&tx).await);
2568 let events = layer.apply(&tx).await;
2569
2570 assert_eq!(events.len(), 0);
2572
2573 let _ = layer.commit();
2574 });
2575 }
2576
2577 #[test]
2578 fn test_double_move_submission() {
2579 let executor = Runner::default();
2580 executor.start(|_| async move {
2581 let mut state = MockState::new();
2582 let (network_secret, master_public) = create_network_keypair();
2583 let seed = create_seed(&network_secret, 1);
2584 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2585 let (signer_a, _) = create_test_actor(1);
2586 let (signer_b, _) = create_test_actor(2);
2587
2588 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2590 assert!(layer.prepare(&tx).await);
2591 layer.apply(&tx).await;
2592 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2593 assert!(layer.prepare(&tx).await);
2594 layer.apply(&tx).await;
2595 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2596 assert!(layer.prepare(&tx).await);
2597 layer.apply(&tx).await;
2598
2599 let changes = layer.commit();
2600 state.apply(changes).await;
2601 let new_seed = create_seed(&network_secret, 102);
2602 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2603 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2604 assert!(layer.prepare(&tx).await);
2605 layer.apply(&tx).await;
2606
2607 let battle_expiry = layer
2608 .view()
2609 .checked_add(MOVE_EXPIRY)
2610 .expect("view overflow");
2611
2612 let move_a1 = create_test_move_ciphertext(master_public, battle_expiry, 1);
2614 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a1));
2615 assert!(layer.prepare(&tx).await);
2616 layer.apply(&tx).await;
2617
2618 let move_a2 = create_test_move_ciphertext(master_public, battle_expiry, 2);
2620 let tx = Transaction::sign(&signer_a, 3, Instruction::Move(move_a2));
2621 assert!(layer.prepare(&tx).await);
2622 let events = layer.apply(&tx).await;
2623
2624 assert_eq!(events.len(), 0);
2626
2627 let _ = layer.commit();
2628 });
2629 }
2630
2631 #[test]
2632 fn test_battle_with_all_health_scenarios() {
2633 let executor = Runner::default();
2634 executor.start(|_| async move {
2635 let mut state = MockState::new();
2636 let (network_secret, master_public) = create_network_keypair();
2637 let seed = create_seed(&network_secret, 1);
2638 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2639 let (signer_a, actor_a) = create_test_actor(1);
2640 let (signer_b, actor_b) = create_test_actor(2);
2641
2642 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2644 assert!(layer.prepare(&tx).await);
2645 layer.apply(&tx).await;
2646 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2647 assert!(layer.prepare(&tx).await);
2648 layer.apply(&tx).await;
2649 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2650 assert!(layer.prepare(&tx).await);
2651 layer.apply(&tx).await;
2652
2653 let changes = layer.commit();
2654 state.apply(changes).await;
2655 let new_seed = create_seed(&network_secret, 102);
2656 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2657 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2658 assert!(layer.prepare(&tx).await);
2659 layer.apply(&tx).await;
2660
2661 let Some(Value::Account(account_a)) = layer.get(&Key::Account(actor_a.clone())).await
2663 else {
2664 panic!("Account A should exist");
2665 };
2666 let Some(Value::Account(account_b)) = layer.get(&Key::Account(actor_b.clone())).await
2667 else {
2668 panic!("Account B should exist");
2669 };
2670
2671 let _max_health_a = account_a.creature.as_ref().unwrap().health();
2672 let _max_health_b = account_b.creature.as_ref().unwrap().health();
2673
2674 let mut nonce_a = 2;
2676 let mut nonce_b = 2;
2677 let mut round = 0;
2678
2679 loop {
2680 let battle_expiry = layer
2681 .view()
2682 .checked_add(MOVE_EXPIRY)
2683 .expect("view overflow");
2684
2685 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 2);
2687 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
2688
2689 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
2690 assert!(layer.prepare(&tx).await);
2691 layer.apply(&tx).await;
2692 nonce_a += 1;
2693 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
2694 assert!(layer.prepare(&tx).await);
2695 layer.apply(&tx).await;
2696 nonce_b += 1;
2697
2698 let changes = layer.commit();
2700 state.apply(changes).await;
2701 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2702 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2703 let signature = create_seed(&network_secret, battle_expiry);
2704 let tx =
2705 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
2706 assert!(layer.prepare(&tx).await);
2707 let events = layer.apply(&tx).await;
2708 nonce_a += 1;
2709
2710 let settled = events.iter().any(|e| matches!(e, Event::Settled { .. }));
2712 if settled {
2713 let Some(Value::Account(final_a)) =
2715 layer.get(&Key::Account(actor_a.clone())).await
2716 else {
2717 panic!("Account A should exist");
2718 };
2719 let Some(Value::Account(final_b)) =
2720 layer.get(&Key::Account(actor_b.clone())).await
2721 else {
2722 panic!("Account B should exist");
2723 };
2724
2725 assert_ne!(
2727 final_a.stats.elo, account_a.stats.elo,
2728 "ELO should have changed for player A"
2729 );
2730 assert_ne!(
2731 final_b.stats.elo, account_b.stats.elo,
2732 "ELO should have changed for player B"
2733 );
2734
2735 let total_games_a =
2737 final_a.stats.wins + final_a.stats.losses + final_a.stats.draws;
2738 let total_games_b =
2739 final_b.stats.wins + final_b.stats.losses + final_b.stats.draws;
2740 assert_eq!(total_games_a, 1, "Player A should have exactly 1 game");
2741 assert_eq!(total_games_b, 1, "Player B should have exactly 1 game");
2742
2743 break;
2744 }
2745
2746 round += 1;
2747 if round > 100 {
2748 panic!("Battle should have ended by now");
2749 }
2750 }
2751
2752 let _ = layer.commit();
2753 });
2754 }
2755
2756 #[test]
2757 fn test_player_a_defends_player_b_attacks() {
2758 let executor = Runner::default();
2759 executor.start(|_| async move {
2760 let mut state = MockState::new();
2761 let (network_secret, master_public) = create_network_keypair();
2762 let seed = create_seed(&network_secret, 1);
2763 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2764 let (signer_a, _) = create_test_actor(1);
2765 let (signer_b, _) = create_test_actor(2);
2766
2767 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2769 assert!(layer.prepare(&tx).await);
2770 layer.apply(&tx).await;
2771 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2772 assert!(layer.prepare(&tx).await);
2773 layer.apply(&tx).await;
2774 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2775 assert!(layer.prepare(&tx).await);
2776 layer.apply(&tx).await;
2777
2778 let changes = layer.commit();
2779 state.apply(changes).await;
2780 let new_seed = create_seed(&network_secret, 102);
2781 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2782 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2783 assert!(layer.prepare(&tx).await);
2784 let events = layer.apply(&tx).await;
2785 let Some(Event::Matched { player_a, .. }) = events.first() else {
2786 panic!("Expected Matched event");
2787 };
2788
2789 let battle_expiry = layer
2790 .view()
2791 .checked_add(MOVE_EXPIRY)
2792 .expect("view overflow");
2793
2794 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
2796 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 3);
2797
2798 let tx = Transaction::sign(&signer_a, 2, Instruction::Move(move_a));
2799 assert!(layer.prepare(&tx).await);
2800 layer.apply(&tx).await;
2801 let tx = Transaction::sign(&signer_b, 2, Instruction::Move(move_b));
2802 assert!(layer.prepare(&tx).await);
2803 layer.apply(&tx).await;
2804
2805 let changes = layer.commit();
2806 state.apply(changes).await;
2807 let new_seed = create_seed(&network_secret, battle_expiry + 1);
2808 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2809 let signature = create_seed(&network_secret, battle_expiry);
2810 let tx = Transaction::sign(&signer_a, 3, Instruction::Settle(signature.signature));
2811 assert!(layer.prepare(&tx).await);
2812 let events = layer.apply(&tx).await;
2813
2814 let move_event = events
2816 .iter()
2817 .find(|e| matches!(e, Event::Moved { .. }))
2818 .unwrap();
2819 if let Event::Moved {
2820 player_a_power,
2821 player_b_power,
2822 ..
2823 } = move_event
2824 {
2825 if signer_a.public_key() == *player_a {
2827 assert!(*player_a_power > 0, "Defense should have positive impact");
2828 assert!(*player_b_power > 0, "Attack should have negative impact");
2829 } else {
2830 assert!(*player_b_power > 0, "Attack should have positive impact");
2831 assert!(*player_a_power > 0, "Defense should have negative impact");
2832 }
2833 }
2834
2835 let _ = layer.commit();
2836 });
2837 }
2838
2839 #[test]
2840 fn test_battle_times_out_after_max_rounds() {
2841 let executor = Runner::default();
2842 executor.start(|_| async move {
2843 let mut state = MockState::new();
2844 let (network_secret, master_public) = create_network_keypair();
2845 let seed = create_seed(&network_secret, 1);
2846 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2847 let (signer_a, actor_a) = create_test_actor(1);
2848 let (signer_b, actor_b) = create_test_actor(2);
2849
2850 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2852 assert!(layer.prepare(&tx).await);
2853 layer.apply(&tx).await;
2854 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2855 assert!(layer.prepare(&tx).await);
2856 layer.apply(&tx).await;
2857 let changes = layer.commit();
2858 state.apply(changes).await;
2859
2860 let seed = create_seed(&network_secret, 2);
2862 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2863 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2864 assert!(layer.prepare(&tx).await);
2865 layer.apply(&tx).await;
2866 let changes = layer.commit();
2867 state.apply(changes).await;
2868
2869 let view = 2 + LOBBY_EXPIRY + 1;
2871 let seed = create_seed(&network_secret, view);
2872 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2873 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
2874 assert!(layer.prepare(&tx).await);
2875 let events = layer.apply(&tx).await;
2876 let Some(Event::Matched { battle, .. }) =
2877 events.iter().find(|e| matches!(e, Event::Matched { .. }))
2878 else {
2879 panic!("Battle should be matched");
2880 };
2881 let changes = layer.commit();
2882 state.apply(changes).await;
2883
2884 let mut nonce_a = 2;
2886 let mut nonce_b = 2;
2887
2888 let mut view = view + 1;
2889 for round in 0..MAX_BATTLE_ROUNDS {
2890 let Some(Value::Battle { expiry, .. }) = state.get(&Key::Battle(*battle)).await
2892 else {
2893 panic!("Battle should exist");
2894 };
2895
2896 let seed = create_seed(&network_secret, view);
2898 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2899 let move_a = create_test_move_ciphertext(master_public, expiry, 0);
2900 let move_b = create_test_move_ciphertext(master_public, expiry, 0);
2901
2902 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
2903 assert!(layer.prepare(&tx).await);
2904 let events = layer.apply(&tx).await;
2905 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2906 nonce_a += 1;
2907
2908 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
2909 assert!(layer.prepare(&tx).await);
2910 let events = layer.apply(&tx).await;
2911 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
2912 nonce_b += 1;
2913 let changes = layer.commit();
2914 state.apply(changes).await;
2915
2916 view = expiry + 1;
2918 let new_seed = create_seed(&network_secret, view);
2919 let new_layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
2920 layer = new_layer;
2921
2922 let expiry_seed = create_seed(&network_secret, expiry);
2923 let tx = Transaction::sign(
2924 &signer_a,
2925 nonce_a,
2926 Instruction::Settle(expiry_seed.signature),
2927 );
2928 assert!(layer.prepare(&tx).await);
2929 let events = layer.apply(&tx).await;
2930 nonce_a += 1;
2931
2932 if let Some(Event::Settled { round, outcome, .. }) =
2934 events.iter().find(|e| matches!(e, Event::Settled { .. }))
2935 {
2936 assert_eq!(*round, MAX_BATTLE_ROUNDS);
2937 assert_eq!(*outcome, Outcome::Draw);
2938
2939 let Some(Value::Account(account_a_final)) =
2941 layer.get(&Key::Account(actor_a.clone())).await
2942 else {
2943 panic!("Account A should exist");
2944 };
2945 let Some(Value::Account(account_b_final)) =
2946 layer.get(&Key::Account(actor_b.clone())).await
2947 else {
2948 panic!("Account B should exist");
2949 };
2950
2951 assert_eq!(account_a_final.stats.draws, 1);
2952 assert_eq!(account_b_final.stats.draws, 1);
2953 assert!(account_a_final.battle.is_none());
2954 assert!(account_b_final.battle.is_none());
2955
2956 return; }
2958
2959 if round < MAX_BATTLE_ROUNDS - 1 {
2961 assert!(
2962 events.iter().any(|e| matches!(e, Event::Moved { .. })),
2963 "Round {round}: Expected Moved event but got {events:?}",
2964 );
2965 }
2966
2967 let changes = layer.commit();
2969 state.apply(changes).await;
2970 view += 1;
2971 }
2972
2973 panic!("Battle should have ended in a draw after MAX_BATTLE_ROUNDS");
2974 });
2975 }
2976
2977 #[test]
2978 fn test_out_of_range_moves_handled_as_no_move() {
2979 let executor = Runner::default();
2980 executor.start(|_| async move {
2981 let mut state = MockState::new();
2982 let (network_secret, master_public) = create_network_keypair();
2983 let seed = create_seed(&network_secret, 1);
2984 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
2985 let (signer_a, actor_a) = create_test_actor(1);
2986 let (signer_b, _) = create_test_actor(2);
2987
2988 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
2990 assert!(layer.prepare(&tx).await);
2991 layer.apply(&tx).await;
2992 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
2993 assert!(layer.prepare(&tx).await);
2994 layer.apply(&tx).await;
2995 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
2996 assert!(layer.prepare(&tx).await);
2997 layer.apply(&tx).await;
2998
2999 let changes = layer.commit();
3000 state.apply(changes).await;
3001 let new_seed = create_seed(&network_secret, 102);
3002 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3003 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3004 assert!(layer.prepare(&tx).await);
3005 layer.apply(&tx).await;
3006 let changes = layer.commit();
3007 state.apply(changes).await;
3008
3009 let Some(Value::Account(account_a)) = state.get(&Key::Account(actor_a.clone())).await
3011 else {
3012 panic!("Account A should exist");
3013 };
3014 let battle_key = account_a.battle.unwrap();
3015
3016 let Some(Value::Battle { player_a, .. }) = state.get(&Key::Battle(battle_key)).await
3018 else {
3019 panic!("Battle should exist");
3020 };
3021
3022 let Some(Value::Battle { expiry, .. }) = state.get(&Key::Battle(battle_key)).await
3024 else {
3025 panic!("Battle should exist");
3026 };
3027
3028 let out_of_range_moves = vec![
3030 5, 10, 100, 255, ];
3035
3036 let mut next_expiry = expiry;
3038 let mut nonce_a = 2;
3039 let mut nonce_b = 2;
3040 for out_of_range_move in out_of_range_moves {
3041 let new_seed = create_seed(&network_secret, next_expiry);
3043 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3044
3045 let move_out_of_range =
3047 create_test_move_ciphertext(master_public, next_expiry, out_of_range_move);
3048 let tx =
3049 Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_out_of_range));
3050 assert!(layer.prepare(&tx).await);
3051 let events = layer.apply(&tx).await;
3052 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
3053 nonce_a += 1;
3054
3055 let move_valid = create_test_move_ciphertext(master_public, next_expiry, 1);
3057 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_valid));
3058 assert!(layer.prepare(&tx).await);
3059 let events = layer.apply(&tx).await;
3060 assert!(events.iter().any(|e| matches!(e, Event::Locked { .. })));
3061 nonce_b += 1;
3062 let changes = layer.commit();
3063 state.apply(changes).await;
3064
3065 let settle_seed = create_seed(&network_secret, next_expiry + 1);
3067 layer = Layer::new(&state, master_public, TEST_NAMESPACE, settle_seed);
3068 let signature = create_seed(&network_secret, next_expiry);
3069 let tx =
3070 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3071 assert!(layer.prepare(&tx).await);
3072 let events = layer.apply(&tx).await;
3073 nonce_a += 1;
3074
3075 let Some(Event::Moved {
3077 expiry,
3078 player_a_move,
3079 player_b_move,
3080 ..
3081 }) = events.first()
3082 else {
3083 panic!("Expected Moved event");
3084 };
3085 if signer_a.public_key() == player_a {
3086 assert_eq!(*player_a_move, 0);
3088 assert_eq!(*player_b_move, 1);
3090 } else {
3091 assert_eq!(*player_a_move, 1);
3092 assert_eq!(*player_b_move, 0);
3093 }
3094 next_expiry = *expiry;
3095
3096 let Some(Value::Account(account)) = layer.get(&Key::Account(actor_a.clone())).await
3098 else {
3099 panic!("Account should exist");
3100 };
3101 assert!(account.battle.is_some(), "Battle should still be ongoing");
3102
3103 let changes = layer.commit();
3105 state.apply(changes).await;
3106 }
3107 });
3108 }
3109
3110 #[test]
3111 fn test_prefer_result_over_draw_when_player_killed_in_last_round() {
3112 let executor = Runner::default();
3113 executor.start(|_| async move {
3114 let mut state = MockState::new();
3116 let (network_secret, master_public) = create_network_keypair();
3117 let seed = create_seed(&network_secret, 1);
3118 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3119 let (signer_a, actor_a) = create_test_actor(1);
3120 let (signer_b, actor_b) = create_test_actor(2);
3121
3122 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
3124 assert!(layer.prepare(&tx).await);
3125 layer.apply(&tx).await;
3126 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
3127 assert!(layer.prepare(&tx).await);
3128 layer.apply(&tx).await;
3129
3130 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
3132 assert!(layer.prepare(&tx).await);
3133 layer.apply(&tx).await;
3134
3135 let changes = layer.commit();
3137 state.apply(changes).await;
3138 let new_seed = create_seed(&network_secret, 102);
3139 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3140 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3141 assert!(layer.prepare(&tx).await);
3142 let events = layer.apply(&tx).await;
3143
3144 let (battle, actual_player_a, _actual_player_b) = if let Event::Matched {
3146 battle,
3147 player_a,
3148 player_b,
3149 ..
3150 } = &events[0]
3151 {
3152 (*battle, player_a.clone(), player_b.clone())
3153 } else {
3154 panic!("Expected Matched event");
3155 };
3156
3157 let is_signer_a_player_a = actual_player_a == actor_a;
3159
3160 let changes = layer.commit();
3162 state.apply(changes).await;
3163
3164 let Some(Value::Battle {
3166 expiry,
3167 round,
3168 player_a,
3169 player_a_max_health,
3170 player_a_pending,
3171 player_a_move_counts,
3172 player_b,
3173 player_b_max_health,
3174 player_b_pending,
3175 player_b_move_counts,
3176 ..
3177 }) = state.get(&Key::Battle(battle)).await
3178 else {
3179 panic!("Battle should exist");
3180 };
3181 state
3182 .insert(
3183 Key::Battle(battle),
3184 Value::Battle {
3185 expiry,
3186 round,
3187 player_a: player_a.clone(),
3188 player_a_max_health,
3189 player_a_health: 1,
3190 player_a_pending,
3191 player_a_move_counts,
3192 player_b: player_b.clone(),
3193 player_b_max_health,
3194 player_b_health: 1,
3195 player_b_pending,
3196 player_b_move_counts,
3197 },
3198 )
3199 .await;
3200
3201 let mut nonce_a = 2;
3203 let mut nonce_b = 2;
3204 let mut view = 103;
3205
3206 for _ in 0..(MAX_BATTLE_ROUNDS - 1) {
3208 let seed = create_seed(&network_secret, view);
3209 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3210
3211 let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(battle)).await
3212 else {
3213 panic!("Battle should exist");
3214 };
3215 let battle_expiry = expiry;
3216
3217 let move_a = create_test_move_ciphertext(master_public, battle_expiry, 0);
3219 let move_b = create_test_move_ciphertext(master_public, battle_expiry, 0);
3220
3221 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(move_a));
3222 assert!(layer.prepare(&tx).await);
3223 layer.apply(&tx).await;
3224 nonce_a += 1;
3225
3226 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(move_b));
3227 assert!(layer.prepare(&tx).await);
3228 layer.apply(&tx).await;
3229 nonce_b += 1;
3230
3231 let changes = layer.commit();
3233 state.apply(changes).await;
3234 view = battle_expiry + 1;
3235 let seed = create_seed(&network_secret, view);
3236 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3237
3238 let signature = create_seed(&network_secret, battle_expiry);
3240 let tx =
3241 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3242 assert!(layer.prepare(&tx).await);
3243 let events = layer.apply(&tx).await;
3244 nonce_a += 1;
3245
3246 assert!(events.iter().any(|e| matches!(e, Event::Moved { .. })));
3248 assert!(!events.iter().any(|e| matches!(e, Event::Settled { .. })));
3249
3250 let changes = layer.commit();
3251 state.apply(changes).await;
3252 view += 1;
3253 }
3254
3255 let seed = create_seed(&network_secret, view);
3258 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3259 let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(battle)).await else {
3260 panic!("Battle should exist");
3261 };
3262
3263 if is_signer_a_player_a {
3266 let attack_move = create_test_move_ciphertext(master_public, expiry, 2); let no_move = create_test_move_ciphertext(master_public, expiry, 0); let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(attack_move));
3271 assert!(layer.prepare(&tx).await);
3272 layer.apply(&tx).await;
3273 nonce_a += 1;
3274
3275 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(no_move));
3276 assert!(layer.prepare(&tx).await);
3277 layer.apply(&tx).await;
3278 } else {
3279 let no_move = create_test_move_ciphertext(master_public, expiry, 0); let attack_move = create_test_move_ciphertext(master_public, expiry, 2); let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(no_move));
3284 assert!(layer.prepare(&tx).await);
3285 layer.apply(&tx).await;
3286 nonce_a += 1;
3287
3288 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(attack_move));
3289 assert!(layer.prepare(&tx).await);
3290 layer.apply(&tx).await;
3291 }
3292
3293 let changes = layer.commit();
3295 state.apply(changes).await;
3296 view = expiry + 1;
3297 let new_seed = create_seed(&network_secret, view);
3298 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3299
3300 let signature = create_seed(&network_secret, expiry);
3302 let tx =
3303 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3304 assert!(layer.prepare(&tx).await);
3305 let events = layer.apply(&tx).await;
3306
3307 let settled_event = events.iter().find(|e| matches!(e, Event::Settled { .. }));
3309 assert!(settled_event.is_some(), "Battle should have been settled");
3310 if let Some(Event::Settled { outcome, round, .. }) = settled_event {
3311 assert_eq!(
3312 *round, MAX_BATTLE_ROUNDS,
3313 "Battle should end at MAX_BATTLE_ROUNDS"
3314 );
3315 assert_eq!(
3316 *outcome,
3317 Outcome::PlayerA,
3318 "Player A should win by killing Player B in the last round"
3319 );
3320 }
3321
3322 let final_account_a =
3324 if let Some(Value::Account(acc)) = layer.get(&Key::Account(actor_a)).await {
3325 acc
3326 } else {
3327 panic!("Account A not found after battle");
3328 };
3329 let final_account_b =
3330 if let Some(Value::Account(acc)) = layer.get(&Key::Account(actor_b)).await {
3331 acc
3332 } else {
3333 panic!("Account B not found after battle");
3334 };
3335 if is_signer_a_player_a {
3336 assert_eq!(
3338 final_account_a.stats.wins, 1,
3339 "Signer A (as Player A) should have won"
3340 );
3341 assert_eq!(final_account_a.stats.draws, 0);
3342 assert_eq!(
3343 final_account_b.stats.losses, 1,
3344 "Signer B (as Player B) should have lost"
3345 );
3346 assert_eq!(final_account_b.stats.draws, 0);
3347 } else {
3348 assert_eq!(
3350 final_account_b.stats.wins, 1,
3351 "Signer B (as Player A) should have won"
3352 );
3353 assert_eq!(final_account_b.stats.draws, 0);
3354 assert_eq!(
3355 final_account_a.stats.losses, 1,
3356 "Signer A (as Player B) should have lost"
3357 );
3358 assert_eq!(final_account_a.stats.draws, 0);
3359 }
3360
3361 let _ = layer.commit();
3362 });
3363 }
3364
3365 #[test]
3366 fn test_defense_moves_never_exceed_max_health() {
3367 let executor = Runner::default();
3368 executor.start(|_| async move {
3369 let mut state = MockState::new();
3371 let (network_secret, master_public) = create_network_keypair();
3372 let seed = create_seed(&network_secret, 1);
3373 let mut layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3374
3375 let (signer_a, _) = create_test_actor(1);
3376 let (signer_b, _) = create_test_actor(2);
3377
3378 let tx = Transaction::sign(&signer_a, 0, Instruction::Generate);
3380 assert!(layer.prepare(&tx).await);
3381 layer.apply(&tx).await;
3382
3383 let tx = Transaction::sign(&signer_b, 0, Instruction::Generate);
3384 assert!(layer.prepare(&tx).await);
3385 layer.apply(&tx).await;
3386
3387 let tx = Transaction::sign(&signer_a, 1, Instruction::Match);
3389 assert!(layer.prepare(&tx).await);
3390 layer.apply(&tx).await;
3391
3392 let changes = layer.commit();
3394 state.apply(changes).await;
3395 let new_seed = create_seed(&network_secret, 102);
3396 layer = Layer::new(&state, master_public, TEST_NAMESPACE, new_seed);
3397
3398 let tx = Transaction::sign(&signer_b, 1, Instruction::Match);
3399 assert!(layer.prepare(&tx).await);
3400 let events = layer.apply(&tx).await;
3401 assert_eq!(events.len(), 1);
3402
3403 let Some(Event::Matched { battle, .. }) = events.first() else {
3405 panic!("Expected Matched event");
3406 };
3407
3408 let mut nonce_a = 2;
3410 let mut nonce_b = 2;
3411 let mut view = 103;
3412 let changes = layer.commit();
3413 state.apply(changes).await;
3414
3415 for round in 0..5 {
3416 let seed = create_seed(&network_secret, view);
3417 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3418
3419 let Some(Value::Battle { expiry, .. }) = layer.get(&Key::Battle(*battle)).await
3420 else {
3421 panic!("Battle should exist");
3422 };
3423 let battle_expiry = expiry;
3424
3425 let defense_move_a = create_test_move_ciphertext(master_public, battle_expiry, 1);
3427 let defense_move_b = create_test_move_ciphertext(master_public, battle_expiry, 1);
3428
3429 let tx = Transaction::sign(&signer_a, nonce_a, Instruction::Move(defense_move_a));
3430 assert!(layer.prepare(&tx).await);
3431 layer.apply(&tx).await;
3432 nonce_a += 1;
3433
3434 let tx = Transaction::sign(&signer_b, nonce_b, Instruction::Move(defense_move_b));
3435 assert!(layer.prepare(&tx).await);
3436 layer.apply(&tx).await;
3437 nonce_b += 1;
3438
3439 let changes = layer.commit();
3441 state.apply(changes).await;
3442 view = battle_expiry + 1;
3443 let seed = create_seed(&network_secret, view);
3444 layer = Layer::new(&state, master_public, TEST_NAMESPACE, seed);
3445
3446 let signature = create_seed(&network_secret, battle_expiry);
3448 let tx =
3449 Transaction::sign(&signer_a, nonce_a, Instruction::Settle(signature.signature));
3450 assert!(layer.prepare(&tx).await);
3451 let events = layer.apply(&tx).await;
3452 nonce_a += 1;
3453
3454 assert!(
3456 events.iter().any(|e| matches!(e, Event::Moved { .. })),
3457 "Should have a Moved event"
3458 );
3459
3460 if let Some(Value::Battle {
3462 player_a_health,
3463 player_a_max_health,
3464 player_b_health,
3465 player_b_max_health,
3466 ..
3467 }) = layer.get(&Key::Battle(*battle)).await
3468 {
3469 assert!(player_a_health <= player_a_max_health);
3471 assert!(player_b_health <= player_b_max_health);
3472 } else {
3473 panic!("Battle should exist after round {round}");
3474 }
3475
3476 let changes = layer.commit();
3477 state.apply(changes).await;
3478 view += 1;
3479 }
3480 });
3481 }
3482}