Smart Contract Reference#
Mergeproof uses a dual-chain architecture: GenLayer handles state machine logic and GitHub verification, while Base (EVM) manages escrow, stakes, and fund settlement.
Table of Contents#
- Architecture Overview
- GenLayer — BountyRegistry
- EVM — Escrow
- EVM — BridgeReceiver
- Cross-Chain Settlement Flow
- Security Model
- Deployment
- Testing
Architecture Overview#
User Wallet
|
├── 1. Deposit funds/stake ──> Base Escrow (EVM)
|
├── 2. Create bounty / Submit PR ──> GenLayer BountyRegistry
| (verifies deposit via eth_call RPC to Base)
|
└── 3. Claim / Settle
GenLayer sends decision ──> Bridge ──> BridgeReceiver ──> Escrow.settle()
The stake-first model requires users to deposit funds on Base before taking actions on GenLayer. GenLayer verifies deposits via cross-chain eth_call RPC, and sends a single settlement message at bounty conclusion.
GenLayer -- BountyRegistry#
File: contracts/genlayer/BountyRegistry.py
The core intelligent contract managing the full bounty lifecycle: identity verification, bounty creation, PR submissions, bug reports, attestations, and settlement.
Data Structures#
Identity#
Maps a wallet address to a GitHub account.
@dataclass
class Identity:
github_user_id: str # GitHub numeric user ID
github_username: str # GitHub login handle
verified_at: str # ISO 8601 timestampBounty#
Represents a bounty attached to a GitHub issue.
@dataclass
class Bounty:
id: str # Unique bounty identifier
repo: str # "owner/repo" format
issue_id: u256 # GitHub issue number
issue_title: str # Issue title from GitHub
atto_amount: u256 # Bounty amount (18 decimals)
token: str # ERC-20 token contract address
stake_ratio_bps: u256 # Stake ratio: 500-2500 (5-25%)
atto_attestation_pool: u256 # Attestation reward pool
review_window_hours: u256 # Review window duration
status: str # open | in_review | completed | cancelled
owner_github_id: str # Bounty creator's GitHub ID
owner_wallet: Address # Bounty creator's wallet
created_at: str # ISO 8601 timestamp
current_submission_id: str # Active submission ID (empty if none)Submission#
A PR submission against a bounty.
@dataclass
class Submission:
id: str # Submission identifier
bounty_id: str # Parent bounty ID
pr_number: u256 # GitHub PR number
pr_title: str # PR title
commit_hash: str # Locked commit hash
submitter_github_id: str # Submitter's GitHub ID
submitter_wallet: Address # Submitter's wallet
atto_staked_amount: u256 # Stake amount
attempt_number: u256 # 1-3 (max 3 attempts)
status: str # active | merged | rejected | abandoned
window_started_at: str # ISO 8601 timestamp
window_ends_at: str # ISO 8601 timestamp
atto_current_bounty: u256 # Bounty after bug reductions
total_reduction_bps: u256 # Cumulative bug reduction in BPSBugReport#
A bug report filed against a submission.
@dataclass
class BugReport:
id: str # Bug report identifier
bounty_id: str # Parent bounty ID
submission_id: str # Target submission ID
commit_hash: str # Must match submission's commit
severity: str # minor | major | critical
description: str # Bug description
evidence_url: str # Link to evidence (gist, etc.)
reporter_github_id: str # Reporter's GitHub ID
reporter_wallet: Address # Reporter's wallet
atto_staked_amount: u256 # Reporter's stake (0.25% of bounty)
validation_status: str # pending | valid | invalid
created_at: str # ISO 8601 timestamp
validated_at: str # ISO 8601 timestamp (empty if pending)Attestation#
A "clean code" attestation for a submission.
@dataclass
class Attestation:
id: str # Attestation identifier
bounty_id: str # Parent bounty ID
submission_id: str # Target submission ID
attestor_github_id: str # Attestor's GitHub ID
attestor_wallet: Address # Attestor's wallet
atto_staked_amount: u256 # Attestor's stake (1% of bounty)
created_at: str # ISO 8601 timestamp
slashed: bool # Whether stake was slashedState Machine#
Bounties follow this lifecycle:
┌──────────────────────────────┐
│ │
v │
create_bounty ──> OPEN ──> submit_pr ──> IN_REVIEW
^ │ │
│ │ ├── claim ──> COMPLETED
│ │ ├── abandon ──> OPEN (reopened)
│ │ ├── auto_reject ──> OPEN (reopened)
│ │ └── retry ──> OPEN ──> IN_REVIEW
│ │
│ └── cancel_bounty ──> CANCELLED
│
└── (reopened after abandon/reject)
State transitions:
| From | To | Trigger | Who |
|---|---|---|---|
| (new) | open | create_bounty() | Bounty owner |
open | in_review | submit_pr() | Any verified user |
in_review | completed | claim() | Submitter (after PR merged) |
in_review | open | abandon() | Submitter (forfeits stake) |
in_review | open | _auto_reject() | System (bounty floor reached) |
in_review | open | retry() | Submitter (new commit) |
open | cancelled | cancel_bounty() | Bounty owner |
Protocol Constants#
| Constant | Value | Description |
|---|---|---|
PROTOCOL_FEE_BPS | 1000 (10%) | Fee on bug rewards and attestation payouts |
BOUNTY_FLOOR_BPS | 5000 (50%) | Minimum bounty value before auto-reject |
MAX_ATTEMPTS | 3 | Maximum submission attempts per user per bounty |
BUG_REPORT_STAKE_BPS | 25 (0.25%) | Bug reporter stake as % of bounty |
ATTESTATION_STAKE_BPS | 100 (1%) | Attestor stake as % of bounty |
ATTESTATION_REWARD_BPS | 50 (0.5%) | Attestor reward as % of bounty |
ATTESTATION_WINDOW_HOURS | 24 | Attestation opens in last 24h of review |
MIN_WINDOW_HOURS | 24 | Minimum review window (production) |
MAX_WINDOW_HOURS | 168 (7 days) | Maximum review window |
Bug severity reductions:
| Severity | Reduction (BPS) | Bounty Impact |
|---|---|---|
minor | 100 | 1% |
major | 300 | 3% |
critical | 1000 | 10% |
Public Methods#
Identity#
start_identity_verification() -> str#
Generate a challenge string for GitHub identity verification. User must post this to their GitHub bio or profile README.
- Access: Write (any wallet)
- Returns: Challenge string (format:
mergeproof-verify:{wallet}:nonce:{nonce})
verify_identity(github_username: str)#
Complete identity verification by checking the challenge on GitHub.
- Access: Write (any wallet)
- Params:
github_username- GitHub login handle - Checks: Challenge present in GitHub bio or profile README
- Reverts: If challenge not found, or GitHub ID already linked to different wallet
get_identity(wallet: str) -> dict#
Get verified identity for a wallet address.
- Access: View
- Returns:
{github_user_id, github_username, verified_at}or{}
dev_register_identity(github_username: str) -> str#
Dev mode only. Register identity without GitHub verification. Only works when bridge_sender is the zero address.
- Access: Write
- Reverts: If not in dev mode
Bounty Management#
create_bounty(bounty_id, repo, issue_id, atto_amount, token, stake_ratio_bps, atto_attestation_pool, review_window_hours) -> str#
Create a bounty for a GitHub issue.
- Access: Write (verified wallet)
- Prerequisite:
Escrow.depositBounty()on Base - Params:
bounty_id: str- Unique identifier (must match Base deposit)repo: str- GitHub repo (owner/repoformat)issue_id: int- GitHub issue numberatto_amount: int- Bounty amount in token's smallest unittoken: str- ERC-20 token addressstake_ratio_bps: int- Stake ratio 500-2500 (5-25%)atto_attestation_pool: int- Attestation pool amount (0 disables attestations)review_window_hours: int- Review window (24-168 hours)
- Validates: Stake ratio range, window range, deposit on Base, issue exists on GitHub
- Returns: The bounty ID
get_bounty(bounty_id: str) -> dict#
Get bounty details.
- Access: View
- Returns: Bounty dict or
{}
list_bounties(status, token, offset, limit, sort) -> dict#
List bounties with filtering and pagination.
- Access: View
- Params:
status: str- Filter by status (optional)token: str- Filter by token address (optional)offset: int- Items to skip (default: 0)limit: int- Max items (default: 50)sort: str-"newest"or"oldest"(default:"newest")
- Returns:
{items: [...], total: int, has_more: bool}
get_bounty_stats() -> dict#
Get bounty counts by status (O(1) operation).
- Access: View
- Returns:
{total, open, in_review, completed, cancelled}
cancel_bounty(bounty_id: str)#
Cancel an open bounty and refund the owner. Cannot cancel if there is an active submission.
- Access: Write (bounty owner only)
- Triggers: Settlement via bridge (full refund)
Submissions#
submit_pr(bounty_id: str, pr_number: int, commit_hash: str) -> str#
Submit a PR for a bounty. First-come-first-served.
- Access: Write (verified wallet)
- Prerequisite:
Escrow.depositStake()withbounty_amount * stake_ratio_bps / 10000 - Validates: Bounty is open, no active submission, attempt limit, stake on Base, PR author matches identity, CI is green
- Returns: Submission ID
retry(bounty_id: str, new_commit_hash: str) -> str#
Retry with a new commit after fixing bugs. Review window must have ended.
- Access: Write (original submitter only)
- Prerequisite: Stake on Base (may need top-up if slashed)
- Validates: Window ended, attempt limit, CI green
- Returns: New submission ID
claim(bounty_id: str)#
Claim bounty after PR is merged. Review window must have ended and all bug reports must be validated.
- Access: Write (submitter only)
- Validates: Window ended, no pending bugs, PR merged on GitHub
- Triggers: Settlement with
CLAIMoutcome
abandon(bounty_id: str)#
Abandon submission and forfeit stake. Bounty reopens for others.
- Access: Write (submitter only)
- Triggers: Settlement with
ABANDONoutcome
get_submission(submission_id: str) -> dict#
Get submission details.
- Access: View
Bug Reports#
report_bug(bounty_id, commit_hash, severity, description, evidence_url) -> str#
Report a bug against the active submission.
- Access: Write (verified wallet)
- Prerequisite:
Escrow.depositStake()with 0.25% of bounty - Params:
bounty_id: str- Target bountycommit_hash: str- Must match active submission's commitseverity: str-"minor"|"major"|"critical"description: str- Bug descriptionevidence_url: str- Link to evidence (optional)
- Validates: Review window open, commit hash matches, severity valid, stake on Base
- Returns: Bug report ID
validate_bug(bug_id: str, is_valid: bool, new_severity: str = "")#
Validate a bug report. Bounty owner only (v1).
- Access: Write (bounty owner)
- Params:
bug_id: str- Bug report IDis_valid: bool- Whether the bug is validnew_severity: str- Optional severity rescope
- Side effects: If valid, reduces bounty. If total reduction >= 50% (floor), triggers auto-reject.
get_bug_reports(submission_id: str) -> list[dict]#
Get all bug reports for a submission.
- Access: View
Attestations#
attest(bounty_id: str) -> str#
Attest that the submission code looks good. Only available in the last 24 hours of the review window.
- Access: Write (verified wallet)
- Prerequisite:
Escrow.depositStake()with 1% of bounty - Validates: Attestation pool not zero, window open (last 24h), not already attested, pool can support another attestor, stake on Base
- Returns: Attestation ID
get_attestations(submission_id: str) -> list[dict]#
Get all attestations for a submission.
- Access: View
GitHub API Integration#
BountyRegistry uses GenLayer's equivalence principle for consensus on external data. Each GitHub API call uses a leader/validator pattern:
- Leader node fetches data from GitHub and extracts stable fields (IDs, titles, merge status)
- Validator nodes independently fetch the same data and compare results
- Only fields that are stable across fetches are compared (e.g., user ID, not timestamps)
API calls made:
GET /users/{username}- Identity verification (user ID, login, bio)GET /repos/{owner}/{repo}/issues/{id}- Issue existence checkGET /repos/{owner}/{repo}/pulls/{id}- PR verification (author, merge status)GET /repos/{owner}/{repo}/commits/{hash}/check-runs- CI status checkGET raw.githubusercontent.com/{user}/{user}/main/README.md- Profile README
RPC verification uses gl.eq_principle.strict_eq() for Base chain calls (deposit/stake verification), ensuring byte-exact matching between leader and validator results.
Dev Mode#
When bridge_sender is the zero address (0x000...000), the contract enters dev mode:
- Identity:
dev_register_identity()available (no GitHub verification) - Stake verification: All
verify_bounty_deposit()andverify_stake()calls returnTrue - GitHub API: All API calls return mock data (issues, PRs, CI)
- Bridge: Settlement messages are still constructed but sent to the zero address
Dev mode is used for local testing and development where external services are unavailable.
EVM -- Escrow#
File: contracts/evm/src/Escrow.sol
Manages all funds on Base: bounty deposits, stakes, and settlement payouts. Inherits from OpenZeppelin's Ownable and ReentrancyGuard.
Types and Structs#
Outcome (enum)#
enum Outcome {
CLAIM, // PR merged, submitter receives bounty
REJECT, // PR not merged, stake forfeited
ABANDON, // Submitter abandoned, stake forfeited
AUTO_REJECT // Bounty floor reached, auto-rejected
}Severity (enum)#
enum Severity {
MINOR, // 1% bounty reduction
MAJOR, // 3% bounty reduction
CRITICAL // 10% bounty reduction
}SettlementDecision#
The decision struct sent from GenLayer via the bridge.
struct SettlementDecision {
bytes32 bountyId;
Outcome outcome;
address submitter;
ValidBug[] validBugs;
address[] invalidBugReporters;
address[] validAttestors;
address[] slashedAttestors;
}BountyDeposit#
struct BountyDeposit {
uint256 amount; // Main bounty amount
uint256 attestationPool; // Attestation reward pool
address token; // ERC-20 token address
address owner; // Bounty creator
bool settled; // Whether already settled
}ValidBug#
struct ValidBug {
address reporter;
Severity severity;
}Deposit Functions#
Called by users before GenLayer actions.
depositBounty(bytes32 bountyId, uint256 amount, uint256 attestationPool, address token)#
Deposit bounty funds with a separate attestation pool.
- Access: Public (anyone)
- Requires:
amount > 0,token != address(0), bounty ID not already used - Token transfer:
amount + attestationPoolviasafeTransferFrom - Emits:
BountyDeposited(bountyId, owner, token, amount, attestationPool)
depositBounty(bytes32 bountyId, uint256 amount, address token)#
Legacy variant. Entire amount treated as bounty, attestation pool is 0.
depositStake(bytes32 bountyId, uint256 amount)#
Deposit stake for a bounty (before submit_pr, report_bug, or attest on GenLayer).
- Access: Public (anyone)
- Requires:
amount > 0, bounty deposit exists - Token: Uses same token as the bounty deposit
- Stakes are cumulative: Multiple deposits add to the staker's total
- Emits:
StakeDeposited(bountyId, staker, amount)
Settlement Functions#
Called exclusively by the bridge.
settle(SettlementDecision calldata decision, uint32 sourceChainId)#
Execute settlement with on-chain payout calculation. All math happens in Escrow based on the decision struct.
- Access:
onlyBridgemodifier - Requires: Source chain matches trusted chain, bounty exists, not already settled
- Emits:
Settled(bountyId, outcome, totalPaid)plus individualPayoutExecutedevents
settle(bytes32 bountyId, uint32 sourceChainId, Payout[] calldata payouts)#
Legacy settlement with pre-calculated payouts. Used for cancellations.
- Access:
onlyBridgemodifier
View Functions#
Called by GenLayer via eth_call RPC for cross-chain verification.
getBountyDeposit(bytes32 bountyId) -> (uint256 amount, address token, address owner)#
Basic deposit info.
getBountyDepositFull(bytes32 bountyId) -> (uint256 amount, uint256 attestationPool, address token, address owner, bool settled)#
Full deposit info including attestation pool and settlement status.
getStake(bytes32 bountyId, address staker) -> uint256#
Stake amount for a specific staker on a bounty.
Internal Settlement Handlers#
Each outcome has a dedicated handler with specific payout logic:
_handleClaim -- PR Merged Successfully#
| Recipient | Receives | Condition |
|---|---|---|
| Submitter | Bounty minus bug reductions | Always |
| Submitter | Stake returned | Always |
| Valid bug reporters | Bug reward (severity-based) + stake returned | If bugs validated |
| Invalid bug reporters | Stake forfeited (to treasury) | If bugs invalidated |
| Attestors (no bugs) | Fixed reward (0.5% of bounty) + stake returned | No valid bugs found |
| Attestors (bugs found) | Stake forfeited (to treasury) | Valid bugs found |
| Bounty owner | Unused attestation pool returned | Always |
_handleReject -- PR Not Merged#
| Recipient | Receives |
|---|---|
| Bounty owner | Bounty floor (50%) + attestation pool |
| Valid bug reporters | Bug rewards + stake returned |
| Invalid bug reporters | Stake forfeited |
| Submitter | Stake forfeited |
| Attestors | Stake forfeited |
_handleAbandon -- Submitter Abandoned#
| Recipient | Receives |
|---|---|
| Bounty owner | Attestation pool returned |
| Valid bug reporters | Bug rewards + stake returned |
| Invalid bug reporters | Stake forfeited |
| Submitter | Stake forfeited |
| Attestors | Stake forfeited |
| Main bounty | Stays in escrow (bounty reopens) |
_handleAutoReject -- Bounty Floor Reached#
| Recipient | Receives |
|---|---|
| Bounty owner | Floor amount (50%) + attestation pool |
| Valid bug reporters | Bug rewards + stake returned |
| Invalid bug reporters | Stake forfeited |
| Submitter | Stake forfeited |
| Attestors | Stake forfeited |
Admin Functions#
All require onlyOwner:
setBridge(address _bridge)- Set the BridgeReceiver contractsetTreasury(address _treasury)- Set the treasury address for protocol feessetTrustedSourceChain(uint32 _chainId)- Set the GenLayer chain ID
Events#
event BountyDeposited(bytes32 indexed bountyId, address indexed owner, address token, uint256 amount, uint256 attestationPool);
event StakeDeposited(bytes32 indexed bountyId, address indexed staker, uint256 amount);
event Settled(bytes32 indexed bountyId, Outcome outcome, uint256 totalPaid);
event PayoutExecuted(bytes32 indexed bountyId, address indexed recipient, uint256 amount, PayoutType payoutType);
event BridgeUpdated(address indexed oldBridge, address indexed newBridge);
event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury);
event TrustedSourceChainUpdated(uint32 indexed oldChainId, uint32 indexed newChainId);EVM -- BridgeReceiver#
File: contracts/evm/src/BridgeReceiver.sol
Receives cross-chain settlement messages from GenLayer and routes them to the Escrow contract. Supports both LayerZero V2 and a direct trusted relayer interface.
Message Flow#
GenLayer BountyRegistry
|
v
BridgeForwarder (GenLayer)
|
├── LayerZero V2 ──> lzReceive() ──> Escrow.settle()
|
└── Trusted Relayer ──> processBridgeMessage() ──> Escrow.settle()
Message Format#
LayerZero envelope:
abi.encode(srcChainId, srcSender, localContract, innerMessage)Decision format (new):
Inner message prefixed with 0x44454349 ("DECI" magic bytes), followed by ABI-encoded SettlementDecision struct.
Legacy format:
Inner message is abi.encode(bountyId, payouts[]).
The receiver auto-detects the format by checking for the "DECI" prefix.
Interface#
File: contracts/evm/src/interfaces/IGenLayerBridgeReceiver.sol
interface IGenLayerBridgeReceiver {
function processBridgeMessage(
uint32 _sourceChainId,
address _sourceContract,
bytes calldata _message
) external;
}BridgeReceiver Admin Functions#
All require onlyOwner:
setTrustedForwarder(uint32 _remoteEid, bytes32 _remoteForwarder)- Set trusted remote forwarder for a chainremoveTrustedForwarder(uint32 _remoteEid)- Remove trusted forwardersetEscrow(address _escrow)- Set the Escrow contractsetTrustedRelayer(address _relayer)- Set trusted relayer for direct message delivery
Entry Points#
lzReceive(Origin, bytes32, bytes, address, bytes) -- LayerZero#
- Access: LayerZero endpoint only (
msg.sender == endpoint) - Validates: Trusted forwarder, escrow set
- Decodes: Bridge envelope, then routes to decision or legacy handler
processBridgeMessage(uint32, address, bytes) -- Direct Relayer#
- Access: Trusted relayer only (
msg.sender == trustedRelayer) - Validates: Escrow set
- Decodes: Inner message directly (no envelope)
Cross-Chain Settlement Flow#
- User action on GenLayer triggers settlement (claim, abandon, auto-reject, cancel)
- BountyRegistry constructs a
SettlementDecisionwith:- Outcome type (CLAIM/REJECT/ABANDON/AUTO_REJECT)
- Submitter address
- List of valid bugs (reporter + severity)
- List of invalid bug reporters (to slash)
- List of valid/slashed attestors
- Bridge delivers message to BridgeReceiver on Base
- BridgeReceiver decodes and calls
Escrow.settle(decision, sourceChainId) - Escrow calculates all payouts on-chain using the decision + protocol constants
- Funds distributed via
safeTransferwith events emitted per payout
Security Model#
Stake Verification Flow#
All actions requiring economic commitment follow the same pattern:
- User deposits funds on Base Escrow (
depositBountyordepositStake) - User calls GenLayer method (
create_bounty,submit_pr,report_bug,attest) - GenLayer verifies deposit via
eth_callRPC to Base Escrow - Action proceeds only if deposit meets minimum requirements
Bridge Trust Assumptions#
- Escrow trusts only the
bridgeaddress (BridgeReceiver) to callsettle() - BridgeReceiver trusts only the LayerZero endpoint or the trusted relayer
- BridgeReceiver validates the remote forwarder is trusted for the source chain
- Escrow validates the
sourceChainIdmatches the configured trusted chain - A single compromised bridge message could execute arbitrary payouts within a bounty's deposited funds
Access Control Patterns#
| Contract | Pattern | Details |
|---|---|---|
| Escrow | Ownable | Admin sets bridge, treasury, trusted chain |
| Escrow | onlyBridge | Only BridgeReceiver can call settle() |
| Escrow | ReentrancyGuard | All deposit and settlement functions |
| BridgeReceiver | Ownable | Admin sets forwarder, escrow, relayer |
| BridgeReceiver | OnlyEndpoint | LayerZero messages must come from endpoint |
| BridgeReceiver | OnlyRelayer | Direct messages must come from trusted relayer |
| BountyRegistry | Identity check | Write methods require verified GitHub identity |
| BountyRegistry | Owner check | validate_bug, cancel_bounty restricted to bounty owner |
Token Safety#
- All ERC-20 transfers use OpenZeppelin's
SafeERC20library - Deposit amounts validated as non-zero
- Settlement marks bounties as
settledto prevent double-settlement
Deployment#
Deployed contract addresses are managed through deployment configuration. See deployment configs for current addresses.
Deploy Sequence#
-
Deploy EVM contracts (Base):
Bashcd contracts/evm && forge script script/Deploy.s.sol --broadcast- Deploy
Escrow(treasury, owner) - Deploy
BridgeReceiver(lzEndpoint, owner) - Configure:
escrow.setBridge(bridgeReceiver),escrow.setTrustedSourceChain(genLayerChainId) - Configure:
bridgeReceiver.setEscrow(escrow),bridgeReceiver.setTrustedForwarder(...)
- Deploy
-
Deploy GenLayer contract:
Bash# Via CLI or GenLayer deployment tools- Deploy
BountyRegistry(baseRpcUrl, escrowAddress, bridgeSender, chainId, treasury)
- Deploy
-
Verify cross-chain connectivity:
- GenLayer can read Escrow state via RPC
- Bridge can deliver messages from GenLayer to BridgeReceiver
Testing#
GenLayer Contract Tests (Direct Mode)#
Fast Python tests (~ms) that run contract logic directly without the simulator.
pnpm test:direct
# or: python -m pytest tests/direct/ -vTests use dev mode (bridge_sender = 0x0...0) which skips external calls:
# conftest.py fixture
dev_bounty_registry = direct_deploy(
bounty_contract_path,
bridge_sender="0x0000000000000000000000000000000000000000",
...
)Test coverage:
- Identity registration (dev mode and production mode rejection)
- Bounty creation with parameter validation
- PR submission flow (first-come-first-served)
- Bug reporting and validation (valid/invalid)
- Bounty floor auto-reject mechanism
- Bounty cancellation (owner only, no active submissions)
- Submission abandonment
EVM Contract Tests (Foundry)#
Solidity tests using Forge with mock contracts.
cd contracts/evm && forge testTest setup uses mock ERC-20 token (6 decimals like USDC) and standard amounts:
- Bounty: $10,000
- Attestation pool: $500
- Submitter stake: $1,000 (10%)
- Bug report stake: $25 (0.25%)
- Attestation stake: $100 (1%)
Test coverage:
- Deposit functions (bounty and stake)
- Settlement outcomes (CLAIM, REJECT, ABANDON, AUTO_REJECT)
- Payout calculations (bug rewards, attestor rewards, protocol fees)
- Access control (onlyBridge, onlyOwner)
- Edge cases (double settlement, insufficient funds)