Contracts

Protocol-level overview of Adrift's smart contracts

Protocol Overview

The protocol is composed of a set of core contracts (enforcing game logic, randomness, and outcomes) and helper contracts (factories, sequencing modules, utilities) that together enable a fully onchain, programmable experience.

Architecture

  • Core contracts: Enforce the fundamental rules and safety guarantees of the game (registration, check-in, outcome, randomness)
  • Helper contracts: Factories, sequencing modules, and utilities that interact with the core, enabling extensibility and upgradability
  • Interfaces: Define contract boundaries, enabling modular upgrades and custom implementations

Core Contracts

Adrift (Game Logic)

The main contract that enforces registration, check-in, disqualification, and winner logic. All gameplay is onchain and transparent.

Key responsibilities:

  • Player registration and check-in
  • Enforcing check-in intervals and disqualification
  • Integrating with outcome and randomness modules
  • Emitting events for all major actions

Interface:

interface IAdrift {
    function register(address player) external;
    function checkIn() external;
    function disqualifyInactivePlayer(address player) external;
    function endGame(address player) external;
    function isPlayerActive(address player) external view returns (bool);
    function setGameStartTime(uint256 startTime) external;
    // ...other admin and view functions
}

CheckInOutcomes (Outcome Logic)

Determines the outcome of each check-in (buff, debuff, disqualification) using onchain randomness. Configurable by an admin.

Key responsibilities:

  • Consumes randomness from a dedicated contract
  • Computes outcome for each player check-in
  • Configurable outcome range and disqualification chance

Interface:

interface ICheckInOutcomes {
    function DISQUALIFIED_OUTCOME() external view returns (int256);
    function OUTCOME_RANGE() external view returns (uint256);
    function getOutcome(address player) external returns (int256);
}

Random (Randomness Source)

In this example app, randomness is sourced via Lit Protocol. The Random contract stores and provides randomness for outcome logic. Only the admin can update the randomness value, which is then consumed by outcome contracts.

Key responsibilities:

  • Stores the latest randomness value
  • Restricts updates to admin/authorized roles

Interface:

interface IRandom {
    function random() external view returns (uint256);
}

Helper Contracts

AdriftFactory (Game Factory)

Deploys new Adrift game instances, enabling upgradability and parallel games. Emits events for easy tracking.

Key responsibilities:

  • Deploys new Adrift contracts
  • Emits creation events

Interface:

interface IAdriftFactory {
    function create(address gameAdmin, address checkInOutcomes) external returns (address);
}

CheckInOutcomesFactory (Outcome Logic Factory)

Deploys new outcome logic contracts, optionally with new randomness sources. Supports upgrades and experimentation.

Key responsibilities:

  • Deploys CheckInOutcomes and Random contracts
  • Emits creation events

Interface:

interface ICheckInOutcomesFactory {
    function create(address outcomesAdmin, address randomness) external returns (address);
    function createWithRandom(address outcomesAdmin, address randomAdmin) external returns (address, address);
}

AdriftBundler (Sequencing/Bundling Module)

Handles transaction bundling and sequencing for the appchain. Integrates with Syndicate's sequencing chain and can be extended for custom logic.

Key responsibilities:

  • Manages a mempool of transactions
  • Processes check-in transactions in bulk
  • Integrates with the sequencing chain

Interface:

interface IAdriftBundler {
    function addRandomness(bytes calldata randomnessTx) external;
    function processTransactionsBulk(bytes[] calldata txns) external;
    function processTransactionUncompressed(bytes calldata txn) external;
    function getMempoolLength() external view returns (uint256);
}

Utilities & Supporting Contracts

  • Decompressor (Stylus, Arbitrum-specific): An Arbitrum Stylus contract that cheaply decompresses calldata onchain. This enables running the node in compressed mode while performing decompression onchain, reducing costs. The decompressed bytes are then fed into decoding logic.
  • RLPTxBreakdown / RLPReader (Solidity): Libraries for decoding Ethereum transactions from decompressed calldata, supporting custom sequencing logic.

Design Decisions

  • Factory pattern: Enables upgradability and parallel deployments, similar to Uniswap's singleton factory and pairs
  • Modular interfaces: All major logic (game, outcome, randomness) is abstracted behind interfaces for easy upgrades
  • Onchain enforcement: All gameplay, randomness, and outcomes are enforced and auditable onchain
  • Events for transparency: All major actions emit events for easy indexing and frontend integration
  • Arbitrum Stylus for decompression: Uses a Stylus contract to decompress calldata onchain, enabling compressed-node mode and minimizing L1 footprint before Solidity decoding via RLPReader.

By following these patterns, you can build powerful, transparent, and upgradable appchains that leverage the full capabilities of Syndicate's modular architecture.