Skip to content

A Cadence protocol allowing users to deposit, earn yield, and win some prizes

Notifications You must be signed in to change notification settings

axiomzen/FlowPrizeVault

Repository files navigation

PrizeLinkedAccounts

A No-Loss Lottery Protocol on Flow

PrizeLinkedAccounts is a prize-linked savings protocol where users deposit tokens to earn guaranteed savings interest while also having chances to win lottery prizes. Users never lose their principal—all prizes come from yield generated by deposits.

⚠️ Audit Status: This contract is currently undergoing security audit.

Table of Contents


Overview

PrizeLinkedAccounts implements a "no-loss lottery" where:

  1. Users deposit tokens into savings pools
  2. Deposits are sent to yield sources (modular DeFi integrations)
  3. Yield is split between savings interest, lottery prizes, and protocol (configurable)
  4. Periodic lottery draws award prizes to depositors (weighted by balance × time)
  5. Users can withdraw their principal + accrued savings interest at any time

Key Properties

  • No Loss: Users always retain access to their principal + savings interest
  • Fair Odds: Lottery odds are weighted by time-weighted average balance (TWAB)
  • Provably Fair: Uses Flow's on-chain randomness (commit-reveal scheme)
  • Gas Efficient: ERC4626-style shares enable O(1) interest distribution
  • Modular: Pluggable yield sources, distribution strategies, and winner selection

Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                              PrizeLinkedAccounts                               │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────────────────┐    ┌─────────────────────┐                     │
│  │ PoolPositionCollection│    │       Admin        │                     │
│  │  (per user)          │    │  (protocol owner)  │                     │
│  └──────────┬──────────┘    └──────────┬─────────┘                     │
│             │                          │                                │
│             ▼                          ▼                                │
│  ┌──────────────────────────────────────────────────────────────────┐  │
│  │                            Pool                                   │  │
│  ├──────────────────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────┐  ┌──────────────────┐  ┌─────────────────┐  │  │
│  │  │SavingsDistributor│  │PrizeDistributor│  │ProtocolDistributor│ │  │
│  │  │ (shares/TWAB)   │  │ (prizes/NFTs)   │  │  (protocol fees) │  │  │
│  │  └────────┬────────┘  └────────┬─────────┘  └────────┬────────┘  │  │
│  │           │                    │                      │           │  │
│  └───────────┼────────────────────┼──────────────────────┼───────────┘  │
│              │                    │                      │              │
│              └────────────────────┼──────────────────────┘              │
│                                   ▼                                     │
│                    ┌─────────────────────────────┐                      │
│                    │    Yield Connector (DeFi)   │                      │
│                    │   (modular yield source)    │                      │
│                    └─────────────────────────────┘                      │
└─────────────────────────────────────────────────────────────────────────┘

Core Contracts

PrizeLinkedAccounts.cdc

The main protocol contract containing:

Component Description
Pool Core resource managing deposits, withdrawals, yield processing, and prize draws
SavingsDistributor ERC4626-style shares vault with epoch-based TWAB tracking
PrizeDistributor Prize pool management, NFT prizes, and draw execution
ProtocolDistributor Protocol fee collection and withdrawals
PoolPositionCollection User-owned resource for interacting with pools
Admin Privileged operations (strategy updates, emergency controls)

Key Mechanisms

Shares Model (ERC4626-Style)

Interest distribution uses a shares-based model for O(1) gas efficiency:

shares = (depositAmount × totalShares) / totalAssets
userValue = (userShares × totalAssets) / totalShares

When yield is distributed, totalAssets increases while shares remain constant—automatically increasing each user's proportional value.

Donation Attack Protection

The implementation includes virtual offset protection against the ERC4626 "inflation attack" (also known as the "donation attack"). This attack allows a malicious first depositor to manipulate the share price when totalShares is small, potentially stealing funds from subsequent depositors.

The protection uses virtual shares and assets:

effectiveShares = totalShares + 1.0
effectiveAssets = totalAssets + 1.0

shares = (assets × effectiveShares) / effectiveAssets
assets = (shares × effectiveAssets) / effectiveShares

This creates "dead" shares/assets that ensure the share price starts near 1:1 and cannot be manipulated through donation attacks, providing defense-in-depth even for future yield connectors that might be permissionless.

📖 Read the full Accounting & Shares documentation →

Time-Weighted Average Balance (TWAB)

Lottery odds are based on share-seconds—shares held multiplied by time held during the epoch:

timeWeightedStake = userShares × (currentTime - lastUpdateTime)
lotteryWeight = cumulativeShareSeconds + currentElapsed

This ensures:

  • Larger deposits = better odds
  • Longer deposits = better odds
  • Late depositors don't get retroactive weight

📖 Read the full TWAB documentation →

Randomness (Commit-Reveal)

Lottery draws use Flow's RandomConsumer for provably fair selection:

  1. startDraw(): Commits to a future block's randomness, snapshots TWAB weights
  2. Wait 1 block: Randomness source block is mined
  3. completeDraw(): Reveals random number, selects winner(s), distributes prizes

Distribution Strategies

Configurable splits between rewards/prize/protocol:

Strategy Description
FixedPercentageStrategy Static split (e.g., 50% savings, 40% lottery, 10% protocol)

Custom strategies can implement the DistributionStrategy interface.

Winner Selection Strategies

Winner selection is a configurable component that determines how prizes are distributed among participants. The following strategies are implemented in the contract:

Strategy Description
WeightedSingleWinner One winner takes all, weighted by TWAB
MultiWinnerSplit Multiple winners with configurable prize splits
FixedPrizeTiers Fixed prize amounts per tier (e.g., 1st: 100, 2nd: 50, 3rd: 25)

Custom strategies can implement the WinnerSelectionStrategy interface.

Emergency Mode

Automatic health monitoring with configurable thresholds:

State Description
Normal All operations enabled
Paused All operations disabled
EmergencyMode Withdrawals only (no deposits/draws)
PartialMode Limited deposits, withdrawals enabled

Auto-triggers based on yield source health, withdrawal failures, or balance thresholds.


Getting Started

Prerequisites

  • Flow CLI installed
  • Flow emulator or testnet access

1. Deploy Contracts

# Start emulator
flow emulator

# Deploy to emulator
flow project deploy --network=emulator

2. Setup User Collection

Users must create a PoolPositionCollection before interacting:

flow transactions send cadence/transactions/prize-linked-accounts/setup_collection.cdc \
  --network=emulator --signer=user-account

3. Deposit into Pool

flow transactions send cadence/transactions/prize-linked-accounts/deposit.cdc \
  --args UInt64:0 UFix64:100.0 \
  --network=emulator --signer=user-account

4. Withdraw from Pool

flow transactions send cadence/transactions/prize-linked-accounts/withdraw.cdc \
  --args UInt64:0 UFix64:50.0 \
  --network=emulator --signer=user-account

5. Query Balance

flow scripts execute cadence/scripts/prize-linked-accounts/get_pool_balance.cdc \
  --args Address:0xUSER_ADDRESS UInt64:0 \
  --network=emulator

6. Start & Complete Lottery Draw

# Start the draw (commits to randomness)
flow transactions send cadence/transactions/prize-linked-accounts/start_draw.cdc \
  --args UInt64:0 \
  --network=emulator

# Wait at least 1 block, then complete the draw
flow transactions send cadence/transactions/prize-linked-accounts/complete_draw.cdc \
  --args UInt64:0 \
  --network=emulator

User Flows

Depositing

// Borrow user's collection
let collection = signer.storage.borrow<&PrizeLinkedAccounts.PoolPositionCollection>(
    from: PrizeLinkedAccounts.PoolPositionCollectionStoragePath
)!

// Withdraw tokens from wallet
let tokens <- flowVault.withdraw(amount: 100.0)

// Deposit into pool (auto-registers on first deposit)
collection.deposit(poolID: 0, from: <-tokens)

Withdrawing

// Withdraw principal + accrued savings interest
let withdrawn <- collection.withdraw(poolID: 0, amount: 50.0)

Checking Balance

let balance = collection.getPoolBalance(poolID: 0)
// balance.deposits: original principal
// balance.savingsEarned: accrued interest
// balance.totalBalance: deposits + savingsEarned

Pool Configuration

Pools are created with a PoolConfig:

let config = PrizeLinkedAccounts.PoolConfig(
    assetType: Type<@FlowToken.Vault>(),
    yieldConnector: connector,          // DeFiActions.Connector
    minimumDeposit: 1.0,                 // Minimum deposit amount
    drawIntervalSeconds: 86400.0,        // 24 hours between draws
    distributionStrategy: strategy,      // FixedPercentageStrategy
    winnerSelectionStrategy: winnerStrategy,
    winnerTrackerCap: nil                // Optional tracker
)

Emergency Configuration

let emergencyConfig = PrizeLinkedAccounts.EmergencyConfig(
    maxEmergencyDuration: 86400.0,       // Auto-recover after 24h
    autoRecoveryEnabled: true,
    minYieldSourceHealth: 0.5,           // Trigger at 50% health
    maxWithdrawFailures: 3,              // Trigger after 3 failures
    partialModeDepositLimit: 100.0,
    minBalanceThreshold: 0.95,           // 95% balance required
    minRecoveryHealth: 0.5
)

Transactions

All transactions are located in cadence/transactions/prize-linked-accounts/:

User Transactions

Transaction Description
setup_collection.cdc Create a PoolPositionCollection (one-time setup)
deposit.cdc Deposit FLOW tokens into a pool
withdraw.cdc Withdraw tokens from a pool (principal + interest)

Lottery Transactions (Permissionless)

Transaction Description
start_draw.cdc Start a lottery draw (commits to randomness)
complete_draw.cdc Complete a draw (reveals winner and distributes prizes)
process_rewards.cdc Trigger yield distribution

Admin Transactions

Transaction Description
create_pool.cdc Create a new PrizeLinkedAccounts pool
withdraw_protocol.cdc Withdraw funds from pool protocol
update_draw_interval.cdc Change time between lottery draws
enable_emergency_mode.cdc Enable emergency mode (withdrawals only)
disable_emergency_mode.cdc Return pool to normal operation

Testing Transactions (Emulator Only)

Transaction Description
setup_test_yield_vault.cdc Create a test vault to simulate yield source
create_test_pool.cdc Create a pool using MockYieldConnector
add_yield_to_pool.cdc Add simulated yield to the test vault

Scripts & Queries

All scripts are located in cadence/scripts/prize-linked-accounts/:

Script Description
get_pool_stats.cdc Pool totals, config, draw status, share price
get_pool_balance.cdc User balance in a pool (deposits, prizes, savings)
get_all_pools.cdc List all pool IDs
get_user_shares.cdc Detailed user share info and TWAB stake
get_draw_status.cdc Lottery timing, prize pool, epoch info
get_protocol_stats.cdc Protocol balance and funding stats
get_emergency_info.cdc Emergency state and details
get_user_pools.cdc All pools a user is registered with
is_registered.cdc Check if user is registered with a pool
preview_deposit.cdc Preview shares received for a deposit amount

Events

Deposits & Withdrawals

  • Deposited(poolID, receiverID, amount)
  • Withdrawn(poolID, receiverID, amount)

Rewards & Distribution

  • RewardsProcessed(poolID, totalAmount, rewardsAmount, lotteryAmount)
  • SavingsYieldAccrued(poolID, amount)
  • LotteryPrizePoolFunded(poolID, amount, source)

Lottery

  • PrizeDrawCommitted(poolID, prizeAmount, commitBlock)
  • PrizesAwarded(poolID, winners, amounts, round)

NFT Prizes

  • NFTPrizeDeposited(poolID, nftID, nftType, depositedBy)
  • NFTPrizeAwarded(poolID, receiverID, nftID, nftType, round)
  • NFTPrizeClaimed(poolID, receiverID, nftID, nftType)

Emergency

  • PoolEmergencyEnabled(poolID, reason, enabledBy, timestamp)
  • PoolEmergencyDisabled(poolID, disabledBy, timestamp)
  • EmergencyModeAutoTriggered(poolID, reason, healthScore, timestamp)
  • EmergencyModeAutoRecovered(poolID, reason, healthScore, duration, timestamp)

Admin Actions

  • DistributionStrategyUpdated(poolID, oldStrategy, newStrategy, updatedBy)
  • WinnerSelectionStrategyUpdated(poolID, oldStrategy, newStrategy, updatedBy)
  • DrawIntervalUpdated(poolID, oldInterval, newInterval, updatedBy)

Security Considerations

Trust Assumptions

Component Trust Level Notes
Admin High Can update strategies, enable emergency mode, withdraw protocol
Yield Source High Protocol depends on yield source solvency
RandomConsumer Core Flow Flow protocol-level randomness

Key Invariants

  1. sum(userShareValues) == totalAssets
  2. Users can always withdraw principal + savingsInterest (when not paused)
  3. Lottery prizes never exceed accumulated lottery yield
  4. totalShares and totalAssets never underflow

Rounding & Dust

  • Share conversions may produce small rounding dust
  • Dust is tracked and periodically sent to protocol
  • Minimum deposit prevents dust-only positions

Access Control

Function Access
deposit() / withdraw() Any user (own collection)
startDraw() / completeDraw() Anyone (if conditions met)
processRewards() Anyone (permissionless)
Admin.* Admin resource holder only
Pool.registerReceiver() Contract-internal only

External Dependencies

Contract Address (Mainnet) Purpose
FungibleToken f233dcee88fe0abe Token standard
NonFungibleToken 1d7e57aa55817448 NFT prize support
RandomConsumer 45caec600164c9e6 On-chain randomness
DeFiActions 92195d814edf9cb0 Yield source connectors (BETA)
FlowTransactionScheduler e467b9dd11fa00df Automated draws

Testing

Run Integration Tests

The test_prize_savings.py script runs a comprehensive integration test suite against the emulator:

# Start the emulator in one terminal
flow emulator start

# Run the test suite in another terminal
python3 test_prize_savings.py

This tests the full contract lifecycle including:

  • Contract deployment and pool creation
  • User collection setup and deposits/withdrawals
  • Yield simulation and reward processing
  • Lottery draw execution (start → complete)
  • Prize winner verification
  • NFT prize lifecycle (deposit, award, claim)
  • Emergency mode and admin operations
  • Multi-user scenarios and lottery fairness
  • Protocol recipient setup and forwarding
  • Draw timing and state management
  • Invalid pool/parameter handling
  • Access control verification
  • NFT edge cases (admin withdrawal, non-winner claims)
  • Precision and boundary cases (UFix64 limits)

Run Unit Tests

flow test cadence/tests/PrizeLinkedAccounts_shares_test.cdc
flow test cadence/tests/PrizeVault_test.cdc

Test Coverage

Area Tests
Core Operations Deposits, withdrawals, yield distribution, lottery draws
Shares Math (ERC4626) Interest distribution, late joiner fairness
NFT Prizes Deposit, award, claim, admin withdrawal, non-winner blocking
Access Control Admin-only operations, user position isolation
Edge Cases Minimum deposits, zero values, invalid pools, concurrent draws
Precision UFix64 limits, 8 decimal precision, large values
Multi-User Multiple depositors, fair prize distribution
Protocol Recipient setup, forwarding verification
Draw Timing Interval enforcement, concurrent prevention, stale recovery
Integration Full lifecycle via test_prize_savings.py

Deployment

Networks

Network Contract Address Status
Emulator f8d6e0586b0a20c7 Development
Testnet c24c9fd9b176ea87 Testing
Mainnet TBD Pending Audit

Deployment Order

  1. PrizeLinkedAccounts
  2. PrizeVaultScheduler (optional, for automated draws)

License

MIT


Contact

For questions or support, please open an issue on this repository.

About

A Cadence protocol allowing users to deposit, earn yield, and win some prizes

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages