Skip to content
6 changes: 6 additions & 0 deletions fault-proof/tests/common/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ use alloy_primitives::{address, Address, B256, U256};
// Anvil predefined accounts
pub const ANVIL_ACCOUNT_0: Address = address!("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266");
pub const ANVIL_ACCOUNT_1: Address = address!("0x70997970C51812dc3A010C7d01b50e0d17dc79C8");
pub const ANVIL_ACCOUNT_2: Address = address!("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC");

// Account private keys
pub const ANVIL_ACCOUNT_0_PRIVATE_KEY: &str =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
pub const ANVIL_ACCOUNT_1_PRIVATE_KEY: &str =
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
pub const ANVIL_ACCOUNT_2_PRIVATE_KEY: &str =
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a";

// Account roles
pub const PROPOSER_ADDRESS: Address = ANVIL_ACCOUNT_0;
Expand All @@ -19,6 +22,9 @@ pub const PROPOSER_PRIVATE_KEY: &str = ANVIL_ACCOUNT_0_PRIVATE_KEY;
pub const CHALLENGER_ADDRESS: Address = ANVIL_ACCOUNT_1;
pub const CHALLENGER_PRIVATE_KEY: &str = ANVIL_ACCOUNT_1_PRIVATE_KEY;

pub const PROVER_ADDRESS: Address = ANVIL_ACCOUNT_2;
pub const PROVER_PRIVATE_KEY: &str = ANVIL_ACCOUNT_2_PRIVATE_KEY;

// Default deployer is the same as proposer
pub const DEPLOYER_PRIVATE_KEY: &str = ANVIL_ACCOUNT_0_PRIVATE_KEY;

Expand Down
46 changes: 43 additions & 3 deletions fault-proof/tests/common/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub enum Role {
Proposer,
Challenger,
Deployer,
Prover,
}

/// Common test environment setup
Expand Down Expand Up @@ -352,6 +353,7 @@ impl TestEnvironment {
Role::Proposer => self.private_keys.proposer,
Role::Challenger => self.private_keys.challenger,
Role::Deployer => self.private_keys.deployer,
Role::Prover => self.private_keys.prover,
};

let wallet = PrivateKeySigner::from_str(key)?;
Expand Down Expand Up @@ -467,9 +469,8 @@ impl TestEnvironment {
pub async fn get_credit(&self, game_address: Address, recipient: Address) -> Result<U256> {
let provider = &self.anvil.provider;
let game = OPSuccinctFaultDisputeGame::new(game_address, provider);
let normal_credit = game.normalModeCredit(recipient).call().await?;
let refund_credit = game.refundModeCredit(recipient).call().await?;
Ok(normal_credit + refund_credit)
let credit = game.credit(recipient).call().await?;
Ok(credit)
}

pub async fn last_game_info(&self) -> Result<(Uint<256, 4>, Address)> {
Expand Down Expand Up @@ -523,12 +524,50 @@ impl TestEnvironment {

Ok(receipt)
}

/// Prove a game with a specific role (e.g., Prover instead of Proposer)
pub async fn prove_game_with_role(
&self,
address: Address,
role: Role,
) -> Result<TransactionReceipt> {
let game = self.fault_dispute_game_with_role(address, role).await?;
let receipt = game
.prove(Bytes::new())
.send()
.await?
.with_required_confirmations(1)
.get_receipt()
.await?;

Ok(receipt)
}

/// Claim bond with a specific role
pub async fn claim_bond_with_role(
&self,
game_address: Address,
recipient: Address,
role: Role,
) -> Result<TransactionReceipt> {
let game = self.fault_dispute_game_with_role(game_address, role).await?;
let receipt = game
.claimCredit(recipient)
.send()
.await?
.with_required_confirmations(1)
.get_receipt()
.await?;

Ok(receipt)
}
}

pub struct TestPrivateKeys {
pub deployer: &'static str,
pub proposer: &'static str,
pub challenger: &'static str,
pub prover: &'static str,
}

impl Default for TestPrivateKeys {
Expand All @@ -537,6 +576,7 @@ impl Default for TestPrivateKeys {
deployer: DEPLOYER_PRIVATE_KEY,
proposer: PROPOSER_PRIVATE_KEY,
challenger: CHALLENGER_PRIVATE_KEY,
prover: PROVER_PRIVATE_KEY,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion fault-proof/tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ pub mod monitor;
pub mod process;

pub use anvil::*;
pub use env::TestEnvironment;
pub use env::{Role, TestEnvironment};
pub use process::*;
75 changes: 75 additions & 0 deletions fault-proof/tests/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,81 @@ mod proposer_sync {
Ok(())
}

/// Tests bond claiming when proposer and prover are different addresses.
///
/// Scenario: ChallengedAndValidProofProvided with distinct proposer/prover
/// - Proposer creates game with initial bond
/// - Challenger challenges with challenger bond
/// - Prover (different address from proposer) proves the claim
/// - Game resolves as DEFENDER_WINS
/// - After finalization, both proposer and prover can claim their respective credits
///
/// This test verifies that the system correctly distributes credits to both parties:
/// - Proposer receives their initial bond back
/// - Prover receives the challenger's bond as reward
#[tokio::test]
async fn test_bond_claim_with_multiple_recipients() -> Result<()> {
use crate::common::{constants::PROVER_ADDRESS, Role};

let (env, _proposer, init_bond) = setup().await?;

let starting_l2_block = env.anvil.starting_l2_block_number;

// Create game as proposer
let block = starting_l2_block + 1;
let root_claim = env.compute_output_root_at_block(block).await?;
env.create_game(root_claim, block, M, init_bond).await?;
let (_, game_address) = env.last_game_info().await?;
tracing::info!("✓ Created game at block {block}");

// Challenge the game
env.challenge_game(game_address).await?;
tracing::info!("✓ Challenger challenged the game");

// Prove with DIFFERENT address (Prover, not Proposer)
env.prove_game_with_role(game_address, Role::Prover).await?;
tracing::info!("✓ Prover (different from proposer) proved the game");

// Resolve the game
env.resolve_game(game_address).await?;
tracing::info!("✓ Resolved game as DEFENDER_WINS");

// Warp past finality delay
env.warp_time(DISPUTE_GAME_FINALITY_DELAY_SECONDS + 1).await?;

// Get credits for both parties
let proposer_credit = env.get_credit(game_address, PROPOSER_ADDRESS).await?;
let prover_credit = env.get_credit(game_address, PROVER_ADDRESS).await?;

tracing::info!(
"Credits - Proposer: {} wei, Prover: {} wei",
proposer_credit,
prover_credit
);

// Verify both have claimable credits
assert!(proposer_credit > U256::ZERO, "Proposer should have credit (bond return)");
assert!(prover_credit > U256::ZERO, "Prover should have credit (challenger's bond)");

// Claim both credits
env.claim_bond(game_address, PROPOSER_ADDRESS).await?;
tracing::info!("✓ Proposer claimed bond");

env.claim_bond_with_role(game_address, PROVER_ADDRESS, Role::Prover).await?;
tracing::info!("✓ Prover claimed reward");

// Verify credits are now zero
let proposer_credit_after = env.get_credit(game_address, PROPOSER_ADDRESS).await?;
let prover_credit_after = env.get_credit(game_address, PROVER_ADDRESS).await?;

assert_eq!(proposer_credit_after, U256::ZERO, "Proposer credit should be 0 after claim");
assert_eq!(prover_credit_after, U256::ZERO, "Prover credit should be 0 after claim");

tracing::info!("✓ Both parties successfully claimed their credits");

Ok(())
}

/// Verifies that defense tasks are spawned in deadline-ascending order.
///
/// This test creates 2 games, then challenges them in reverse order (game 1 first, then
Expand Down
Loading