vault_factory

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "contracts/errors.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/**
 * @title PAI Token System Custom Error Definitions
 * @author EdgeServo
 * @notice Defines all custom errors used throughout the PAI distribution system
 * @dev Uses Solidity 0.8.4+ custom error feature to save gas costs
 *
 * Advantages:
 * - Gas optimization: significantly reduces deployment and runtime costs compared to require strings
 * - Type safety: compile-time checking, avoids string typos
 * - Code clarity: centralized management, easier to maintain and document
 * - Chain-friendly: error selectors only occupy 4 bytes, more compact than strings
 *
 * Usage examples:
 * ```solidity
 * if (amount == 0) revert ZeroAmount();
 * if (msg.sender != owner) revert Unauthorized();
 * ```
 */

// ========== General Validation Errors ==========

/**
 * @notice Zero address error
 * @dev Triggered when function parameter or return value is zero address (0x0)
 *
 * Trigger scenarios:
 * - Constructor parameter validation fails
 * - Zero address passed when setting critical contract addresses
 * - Query result is zero address but should not be
 *
 * Usage example:
 * ```solidity
 * if (_token == address(0)) revert ZeroAddress();
 * ```
 */
error ZeroAddress();

/**
 * @notice Zero amount error
 * @dev Triggered when token amount, value or count is zero but business logic requires non-zero
 *
 * Trigger scenarios:
 * - Attempt to transfer zero amount of tokens
 * - Allocation authorization amount is zero
 * - Vault balance is zero when attempting withdrawal
 *
 * Usage example:
 * ```solidity
 * if (amount == 0) revert ZeroAmount();
 * if (balance == 0) revert ZeroAmount();
 * ```
 */
error ZeroAmount();

/**
 * @notice Invalid amount error
 * @dev Triggered when amount exceeds valid range, incorrect format or doesn't satisfy business rules
 *
 * Trigger scenarios:
 * - Signature length is not 65 bytes
 * - Signature parameter v value is not 27 or 28
 * - Amount exceeds maximum limit
 * - Amount format doesn't meet requirements
 *
 * Usage example:
 * ```solidity
 * if (signature.length != 65) revert InvalidAmount();
 * if (v != 27 && v != 28) revert InvalidAmount();
 * ```
 */
error InvalidAmount();

/**
 * @notice Unauthorized access error
 * @dev Triggered when caller lacks required permissions or operation is unauthorized
 *
 * Trigger scenarios:
 * - Non-owner attempts to call management functions
 * - Non-EOA attempts to deploy vault
 * - Unregistered vault attempts to execute operations
 * - Reentrancy attack detection
 * - Vault not initialized when calling protected functions
 * - Attempting to operate resources not owned by caller
 *
 * Usage example:
 * ```solidity
 * if (msg.sender != owner) revert Unauthorized();
 * if (!_initialized) revert Unauthorized();
 * if (_locked == _ENTERED) revert Unauthorized();
 * ```
 */
error Unauthorized();

/**
 * @notice Entity already exists error
 * @dev Triggered when attempting to create or register an already existing entity
 *
 * Trigger scenarios:
 * - User already owns a vault, attempts to deploy again
 * - Vault already registered, duplicate registration
 * - Attempting to set configuration that can only be set once
 * - Duplicate vault initialization
 *
 * Usage example:
 * ```solidity
 * if (user_vaults[user] != address(0)) revert AlreadyExists();
 * if (registered_vaults[vault]) revert AlreadyExists();
 * if (distributor != address(0)) revert AlreadyExists();
 * ```
 */
error AlreadyExists();

// ========== Transfer Operation Errors ==========

/**
 * @notice Transfer failed error
 * @dev Triggered when ERC20 token transfer operation returns false or execution fails
 *
 * Trigger scenarios:
 * - transfer() returns false
 * - transferFrom() returns false
 * - Insufficient balance causing transfer failure
 * - Insufficient allowance causing transferFrom failure
 * - Allocation execution failure
 *
 * Usage example:
 * ```solidity
 * bool success = token.transfer(to, amount);
 * if (!success) revert TransferFailed();
 * ```
 *
 * Note:
 * - Some ERC20 tokens revert on failure instead of returning false
 * - Recommend using SafeERC20 library for safer transfer operations
 */
error TransferFailed();

// ========== Signature Verification Errors ==========

/**
 * @notice Invalid signature error
 * @dev Triggered when EIP-712 signature verification fails
 *
 * Trigger scenarios:
 * - Signer is not the authorized signer
 * - Signature data has been tampered with
 * - Signature for wrong data structure
 * - ecrecover recovery fails (returns zero address)
 * - Wrong private key used for signing
 *
 * Usage example:
 * ```solidity
 * address recovered = ecrecover(digest, v, r, s);
 * if (recovered != signer) revert InvalidSignature();
 * if (recovered == address(0)) revert InvalidSignature();
 * ```
 *
 * Security recommendations:
 * - Always verify ecrecover return value is not zero address
 * - Use correct EIP-712 domain separator
 * - Ensure nonce mechanism is correctly implemented
 */
error InvalidSignature();

/**
 * @notice Signature expired error
 * @dev Triggered when signature's validity deadline has passed
 *
 * Trigger scenarios:
 * - Current block timestamp > deadline
 * - Signature created beyond validity period
 * - Network congestion causing transaction confirmation delay
 *
 * Usage example:
 * ```solidity
 * if (block.timestamp > deadline) revert SignatureExpired();
 * ```
 *
 * Considerations:
 * - deadline should have reasonable buffer time
 * - Consider network congestion may cause delays
 * - Expired signatures cannot be reused, must regenerate
 */
error SignatureExpired();
"
    },
    "contracts/interfaces.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

/**
 * @title PAI Token System Interface Definitions
 * @author EdgeServo
 * @notice Defines all contract interaction interfaces in PAI token distribution system
 * @dev Includes ERC20 standard interface and internal system contract interfaces
 *
 * Interface descriptions:
 * - i_erc20: Standard ERC20 token interface (simplified version)
 * - i_vault: User vault contract interface
 * - i_distributor: Distributor contract interface
 * - i_project_vault: Project vault contract interface
 * - i_vault_factory: Vault factory contract interface
 */

// ========== ERC20 Token Interface ==========

/**
 * @title ERC20 Token Interface (Simplified)
 * @notice Defines core ERC20 standard functions needed by PAI system
 * @dev Only includes core functions used by system, not complete ERC20 standard
 */
interface i_erc20 {
    /**
     * @notice Query token balance of specified account
     * @dev Standard ERC20 function
     *
     * @param account Account address to query
     * @return Account's token holdings (in wei)
     *
     * Usage:
     * - Vault queries its own PAI balance
     * - Query reward token balance
     * - Verify sufficient balance before transfer
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @notice Transfer tokens to specified address
     * @dev Standard ERC20 function, transfers from caller's account
     *
     * @param to Recipient address
     * @param amount Transfer amount (in wei)
     * @return Whether transfer succeeded
     *
     * Requirements:
     * - Caller's balance must be >= amount
     * - to address cannot be zero address
     *
     * Usage:
     * - Vault transfers PAI to user
     * - Project vault allocates tokens to user vault
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @notice Transfer tokens from one address to another
     * @dev Standard ERC20 function, requires prior approval
     *
     * @param from Source address (token owner)
     * @param to Recipient address
     * @param amount Transfer amount (in wei)
     * @return Whether transfer succeeded
     *
     * Requirements:
     * - from address balance must be >= amount
     * - Caller must have sufficient allowance
     * - to address cannot be zero address
     *
     * Usage:
     * - Vault withdraws reward tokens (from awarder to accepter)
     * - Requires awarder to pre-call approve() for authorization
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

// ========== User Vault Interface ==========

/**
 * @title User Vault Contract Interface
 * @notice Defines externally exposed functions of user vault
 * @dev Used for distributor and project vault to interact with user vaults
 */
interface i_vault {
    /**
     * @notice Get vault owner address
     * @dev Read-only function, returns owner set at vault creation
     *
     * @return Ethereum address of vault owner
     *
     * Usage:
     * - Distributor verifies registrant identity
     * - Frontend displays vault ownership information
     * - Other contracts verify operation permissions
     */
    function owner() external view returns (address);

    /**
     * @notice Complete vault initialization
     * @dev Called by distributor after successful registration, marks vault as usable
     *
     * Access control:
     * - Can only be called by distributor contract
     * - Can only be called once
     *
     * State changes:
     * - Set _initialized flag to true
     * - Vault becomes operational
     *
     * Usage:
     * - Complete final step of vault registration
     * - Activate all vault functions
     */
    function complete_initialization() external;

    /**
     * @notice Withdraw third-party reward tokens
     * @dev Called by distributor, transfers rewards from awarder to accepter
     *
     * @param token_address Reward token contract address
     *
     * Access control:
     * - Can only be called by distributor contract
     * - Vault must be initialized
     *
     * Restrictions:
     * - Cannot withdraw PAI tokens
     * - Awarder must have sufficient balance
     *
     * Usage:
     * - Distribute reward tokens provided by project party
     * - Handle airdrops and other incentive activities
     */
    function withdraw_award(address token_address) external;
}

// ========== Distributor Interface ==========

/**
 * @title Distributor Contract Interface
 * @notice Defines core functions exposed by distributor
 * @dev Used for vaults and project vault to interact with distributor
 */
interface i_distributor {
    /**
     * @notice Get project vault address
     * @dev Read-only function, returns project vault address in system
     *
     * @return Project vault contract address
     *
     * Usage:
     * - Vault queries project vault location
     * - Get award role configuration
     * - Verify system configuration completeness
     */
    function project_vault() external view returns (address);

    /**
     * @notice Execute token allocation operation
     * @dev Verify signature then transfer from project vault to user vault
     *
     * @param amount Amount of tokens to allocate (in wei)
     * @param deadline Signature validity deadline timestamp
     * @param signature EIP-712 signature data (65 bytes)
     * @return Whether allocation succeeded
     *
     * Access control:
     * - Can only be called by registered vault contracts
     * - Requires valid authorization signature
     *
     * Verification flow:
     * 1. Check if vault is registered
     * 2. Verify signature has not expired
     * 3. Verify EIP-712 signature
     * 4. Call project vault to execute transfer
     *
     * Usage:
     * - User vault claims allocation quota
     * - Implement signature-authorized token distribution
     */
    function execute_allocation(
        uint256 amount,
        uint256 deadline,
        bytes calldata signature
    ) external returns (bool);

    /**
     * @notice Distribute reward tokens
     * @dev Coordinate reward token transfer from awarder to accepter
     *
     * @param vault_address Vault receiving rewards
     * @param token_address Reward token contract address
     *
     * Access control:
     * - Can only be called by project vault contract
     * - Target vault must be registered
     *
     * Execution flow:
     * - Verify permissions and vault status
     * - Call vault's withdraw_award() function
     *
     * Usage:
     * - Project vault initiates reward distribution
     * - Handle third-party token rewards
     */
    function distribute_award(
        address vault_address,
        address token_address
    ) external;
}

// ========== Project Vault Interface ==========

/**
 * @title Project Vault Contract Interface
 * @notice Defines core functions exposed by project vault
 * @dev Used for distributor and user vaults to interact with project vault
 */
interface i_project_vault {
    /**
     * @notice Register user vault to system
     * @dev Called by distributor during user registration, establishes vault-user association
     *
     * @param vault_address Vault address to register
     * @param user Vault owner (user address)
     *
     * Access control:
     * - Can only be called by distributor contract
     *
     * State changes:
     * - Record vault and user mapping relationship
     * - May initialize reward configuration, etc.
     *
     * Usage:
     * - Complete vault registration process
     * - Establish user and vault association
     * - Initialize user-related configuration
     */
    function register_vault(address vault_address, address user) external;

    /**
     * @notice Allocate tokens to user vault
     * @dev Transfer specified amount of PAI tokens to user vault
     *
     * @param user_vault User vault address receiving tokens
     * @param amount Allocation amount (in wei)
     * @return Whether allocation succeeded
     *
     * Access control:
     * - Can only be called by distributor contract
     *
     * Preconditions:
     * - Project vault has sufficient balance
     * - User vault is registered
     *
     * Usage:
     * - Execute actual token transfer
     * - Complete final step of allocation authorization
     */
    function allocate_to_user(
        address user_vault,
        uint256 amount
    ) external returns (bool);

    /**
     * @notice Get award role configuration for specified vault
     * @dev Returns reward token awarder and accepter addresses
     *
     * @param vault_address Vault address
     * @return awarder Reward awarder address (token holder)
     * @return accepter Reward accepter address (final beneficiary)
     *
     * Usage:
     * - Vault queries reward configuration
     * - Determine reward token transfer path
     * - Support flexible reward distribution mechanism
     *
     * Note:
     * - Awarder needs to pre-approve vault sufficient allowance
     * - Accepter may be user themselves or other designated address
     */
    function get_award_roles(
        address vault_address
    ) external view returns (address awarder, address accepter);
}

// ========== Vault Factory Interface ==========

/**
 * @title Vault Factory Contract Interface
 * @notice Defines validation functions exposed by vault factory
 * @dev Used for distributor to verify vault legitimacy
 */
interface i_vault_factory {
    /**
     * @notice Verify if address is a valid deployed vault
     * @dev Check if address was deployed by this factory via CREATE2
     *
     * @param vault_address Vault address to verify
     * @return Whether it is a valid vault address
     *
     * Verification logic:
     * - Check deployed_vaults mapping
     * - Confirm vault was deployed by factory
     *
     * Usage:
     * - Distributor verifies vault legitimacy
     * - Prevent registration of unauthorized vaults
     * - Ensure vault code is trustworthy
     *
     * Note:
     * - Only verifies deployment source, not initialization status
     * - Returns true means deployed by factory, but may not be registered
     */
    function is_valid_vault(address vault_address) external view returns (bool);
}
"
    },
    "contracts/vault_factory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import "./vault.sol";
import "./errors.sol";

/**
 * @title PAI Vault Factory Contract
 * @author EdgeServo
 * @notice Uses CREATE2 opcode to create user vaults with deterministic addresses
 * @dev Implements singleton pattern (one vault per user) and address predictability
 *
 * Core Features:
 * - CREATE2 deterministic deployment: predictable vault addresses
 * - Single vault per user: prevents duplicate deployments
 * - EOA restriction: only external accounts can create vaults
 * - Salt uniqueness: multiple entropy sources ensure no address conflicts
 *
 * Workflow:
 * 1. Platform deploys factory and sets distributor
 * 2. Users call deploy_vault() to create personal vaults
 * 3. Factory uses CREATE2 to generate deterministic addresses
 * 4. Records deployment information for future verification
 */
contract vault_factory {
    // ========== Immutable State Variables ==========

    /// @notice Platform owner address
    /// @dev Account that deployed the factory, has management privileges
    address public immutable platform_owner;

    /// @notice PAI token contract address
    /// @dev Token type that all vaults will manage
    address public immutable pai_token;

    // ========== Mutable State Variables ==========

    /// @notice Distributor contract address
    /// @dev Responsible for signature verification and allocation coordination, cannot be changed after initialization
    address public distributor;

    /// @notice Global deployment counter
    /// @dev Used to generate unique salts, prevents address conflicts
    uint256 public deployment_counter;

    // ========== Mapping Storage ==========

    /// @notice Verification mapping for deployed vaults
    /// @dev vault address => whether deployed by this factory
    mapping(address => bool) public deployed_vaults;

    /// @notice User to vault unidirectional mapping
    /// @dev user address => vault address (each user can only have one vault)
    mapping(address => address) public user_vaults;

    // ========== Constants ==========

    /// @notice Salt prefix constant
    /// @dev Used for CREATE2 salt calculation, ensures domain isolation
    bytes32 public constant SALT_PREFIX = keccak256("PAI_VAULT");

    // ========== Events ==========

    /**
     * @notice Vault successfully deployed event
     * @param user Vault owner address
     * @param vault Newly deployed vault address
     * @param deployment_number Global deployment sequence number
     */
    event VaultDeployed(
        address indexed user,
        address indexed vault,
        uint256 deployment_number
    );

    /**
     * @notice Distributor address updated event
     * @param new_distributor Newly set distributor address
     */
    event DistributorUpdated(address indexed new_distributor);

    // ========== Constructor ==========

    /**
     * @notice Create vault factory instance
     * @dev Deployer automatically becomes platform owner
     *
     * @param _token PAI token contract address
     *
     * Requirements:
     * - Token address cannot be zero address
     *
     * Reverts:
     * - {ZeroAddress} if token address is invalid
     */
    constructor(address _token) {
        // Parameter validation: ensure token address is valid
        if (_token == address(0)) revert ZeroAddress();

        // Set immutable variables
        platform_owner = msg.sender;
        pai_token = _token;
    }

    // ========== Management Functions ==========

    /**
     * @notice Set distributor contract address (one-time operation)
     * @dev Must be completed before users start deploying vaults
     *
     * @param _distributor Distributor contract address
     *
     * Access control:
     * - Can only be called by platform owner
     * - Can only be set once (cannot be changed)
     *
     * State changes:
     * - Set distributor address
     *
     * Reverts:
     * - {Unauthorized} if caller is not platform owner
     * - {ZeroAddress} if distributor address is invalid
     * - {AlreadyExists} if distributor already set
     *
     * Emits:
     * - {DistributorUpdated} distributor set successfully
     */
    function set_distributor(address _distributor) external {
        // Permission verification: only platform owner can set
        if (msg.sender != platform_owner) revert Unauthorized();

        // Parameter validation: distributor address must be valid
        if (_distributor == address(0)) revert ZeroAddress();

        // State verification: prevent duplicate setting
        if (distributor != address(0)) revert AlreadyExists();

        // Set distributor address
        distributor = _distributor;

        // Emit update event
        emit DistributorUpdated(_distributor);
    }

    // ========== Core Deployment Function ==========

    /**
     * @notice Deploy personal vault for caller (using CREATE2)
     * @dev Each user can only deploy one vault, must be called directly by EOA
     *
     * Execution flow:
     * 1. Verify distributor has been set
     * 2. Check if user already has a vault
     * 3. Confirm caller is EOA (prevent contract calls)
     * 4. Increment counter to generate unique sequence number
     * 5. Calculate CREATE2 salt (multiple entropy sources)
     * 6. Deploy new vault contract
     * 7. Record deployment information
     *
     * Salt composition:
     * - SALT_PREFIX: domain prefix
     * - user: user address
     * - deployment_number: global sequence number
     * - block.timestamp: block timestamp
     * - block.number: block height
     *
     * @return vault_address Newly deployed vault address
     *
     * Access control:
     * - Can only be called by EOA (external account)
     * - Each user can only call once
     *
     * Preconditions:
     * - distributor must be set
     * - Caller does not yet own a vault
     *
     * Security measures:
     * - EOA verification (msg.sender == tx.origin)
     * - Singleton check (user_vaults mapping)
     * - Multiple entropy sources prevent address conflicts
     *
     * Reverts:
     * - {ZeroAddress} if distributor not set
     * - {AlreadyExists} if user already has a vault
     * - {Unauthorized} if caller is not EOA
     *
     * Emits:
     * - {VaultDeployed} vault deployed successfully
     */
    function deploy_vault() external returns (address vault_address) {
        address user = msg.sender;

        // Precondition check: ensure distributor is configured
        if (distributor == address(0)) revert ZeroAddress();

        // Singleton check: each user can only have one vault
        if (user_vaults[user] != address(0)) revert AlreadyExists();

        // Security check: ensure external account call (prevent contract batch creation)
        // Note: This may restrict use of multi-sig wallets and other contract wallets
        if (user != tx.origin) revert Unauthorized();

        // Generate unique deployment sequence number
        uint256 deployment_number;
        unchecked {
            // Increment counter (overflow impossible)
            deployment_number = ++deployment_counter;
        }

        // Calculate CREATE2 salt
        // Use multiple entropy sources to ensure uniqueness and unpredictability
        bytes32 salt = keccak256(
            abi.encodePacked(
                SALT_PREFIX, // Domain prefix
                user, // User address
                deployment_number, // Global sequence number
                block.timestamp, // Timestamp
                block.number // Block height
            )
        );

        // Deploy vault using CREATE2
        // Syntax: new Contract{salt: bytes32}(constructor_args)
        vault_address = address(
            new vault{salt: salt}(user, pai_token, distributor)
        );

        // Record deployment information to mappings
        deployed_vaults[vault_address] = true; // Mark as valid vault
        user_vaults[user] = vault_address; // Associate user and vault

        // Emit deployment event
        emit VaultDeployed(user, vault_address, deployment_number);

        return vault_address;
    }

    // ========== Query Functions ==========

    /**
     * @notice Pre-calculate vault address (simulate CREATE2)
     * @dev Can be used to verify address before deployment, but requires accurate parameters
     *
     * @param user User address
     * @param deployment_number Deployment sequence number (deployment_counter + 1)
     * @param timestamp Expected block timestamp
     * @param blockNumber Expected block number
     * @return Predicted vault address
     *
     * Note:
     * - Timestamp and block number are difficult to predict accurately
     * - Mainly used for testing and verification
     * - Actual address depends on block state at deployment time
     */
    function compute_vault_address(
        address user,
        uint256 deployment_number,
        uint256 timestamp,
        uint256 blockNumber
    ) external view returns (address) {
        // Reconstruct same salt as deployment
        bytes32 salt = keccak256(
            abi.encodePacked(
                SALT_PREFIX,
                user,
                deployment_number,
                timestamp,
                blockNumber
            )
        );

        // Calculate initialization code hash
        // type(Contract).creationCode returns contract bytecode
        bytes32 bytecodeHash = keccak256(
            abi.encodePacked(
                type(vault).creationCode,
                abi.encode(user, pai_token, distributor)
            )
        );

        // CREATE2 address calculation formula:
        // keccak256(0xff ++ deployer_address ++ salt ++ keccak256(init_code))
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff), // CREATE2 prefix
                address(this), // Deployer address (factory)
                salt, // Salt value
                bytecodeHash // Initialization code hash
            )
        );

        // Take last 20 bytes of hash as address
        return address(uint160(uint256(hash)));
    }

    /**
     * @notice Verify if address is a valid deployed vault
     * @dev Used by other contracts to verify vault authenticity
     *
     * @param vault_address Vault address to verify
     * @return Whether it is a valid vault deployed by this factory
     */
    function is_valid_vault(
        address vault_address
    ) external view returns (bool) {
        return deployed_vaults[vault_address];
    }

    /**
     * @notice Query vault address for specified user
     * @dev Returns zero address if user has not created a vault
     *
     * @param user User address
     * @return User's vault address (if exists)
     */
    function get_user_vault(address user) external view returns (address) {
        return user_vaults[user];
    }

    /**
     * @notice Get deployment statistics
     * @dev Returns global statistics and caller's personal information
     *
     * @return total_deployed Total number of deployed vaults
     * @return user_vault Caller's vault address (may be zero address)
     */
    function get_deployment_stats()
        external
        view
        returns (uint256 total_deployed, address user_vault)
    {
        return (deployment_counter, user_vaults[msg.sender]);
    }

    /**
     * @notice Get next deployment sequence number
     * @dev Used for frontend display or address prediction
     *
     * @return Next deployment_number to be used
     */
    function get_next_deployment_number() external view returns (uint256) {
        return deployment_counter + 1;
    }
}
"
    },
    "contracts/vault.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;

import "./interfaces.sol";
import "./errors.sol";

/**
 * @title PAI User Vault Contract
 * @author EdgeServo
 * @notice Secure vault for managing PAI tokens and rewards for individual users
 * @dev Implements reentrancy protection, initialization control, and ownership verification mechanisms
 *
 * Main Features:
 * - Receive and store PAI token allocations
 * - Support user withdrawals to wallets
 * - Handle third-party token reward distribution
 * - Prevent reentrancy attacks and unauthorized access
 */
contract vault {
    // ========== Immutable State Variables ==========

    /// @notice Vault owner address (sole authorized user)
    /// @dev Set at construction time, cannot be changed
    address public immutable owner;

    /// @notice PAI token contract address
    /// @dev Primary token type managed by the vault
    address public immutable pai_token;

    /// @notice Distributor contract address
    /// @dev Trusted contract responsible for signature verification and allocation coordination
    address public immutable distributor;

    // ========== Mutable State Variables ==========

    /// @notice Total PAI tokens withdrawn by user from vault to wallet
    /// @dev Only counts amounts withdrawn via withdraw() function
    uint256 public total_withdrawn;

    /// @notice Total PAI tokens claimed by user from project vault to this vault
    /// @dev Only counts amounts claimed via claim_allocation() function
    uint256 public total_claimed;

    /// @notice Vault initialization status flag
    /// @dev Prevents unregistered vaults from being used
    bool private _initialized;

    /// @notice Reentrancy lock state variable
    /// @dev Implements reentrancy protection using state machine pattern
    uint256 private _locked;

    // ========== Constants ==========

    /// @notice Reentrancy lock unlocked state
    uint256 private constant _NOT_ENTERED = 1;

    /// @notice Reentrancy lock locked state
    uint256 private constant _ENTERED = 2;

    // ========== Events ==========

    /**
     * @notice User withdrew PAI tokens event
     * @param to Recipient address (owner wallet)
     * @param amount Withdrawal amount
     */
    event Withdrawn(address indexed to, uint256 amount);

    /**
     * @notice Reward token distribution event
     * @param token Reward token contract address
     * @param from Reward source address (awarder)
     * @param to Reward recipient address
     * @param amount Reward amount
     */
    event AwardDistributed(
        address indexed token,
        address indexed from,
        address indexed to,
        uint256 amount
    );

    /**
     * @notice User claimed PAI allocation event
     * @param amount Claimed amount
     */
    event Claimed(uint256 amount);

    /**
     * @notice Vault initialization completed event
     * @param distributor Distributor contract address
     */
    event Initialized(address indexed distributor);

    // ========== Constructor ==========

    /**
     * @notice Create new user vault instance
     * @dev Called by vault_factory via CREATE2 to ensure address uniqueness
     *
     * @param _owner Vault owner address (user wallet address)
     * @param _token PAI token contract address
     * @param _distributor Distributor contract address
     *
     * Requirements:
     * - All parameters must not be zero address
     *
     * Reverts:
     * - {ZeroAddress} if any parameter is zero address
     */
    constructor(address _owner, address _token, address _distributor) {
        // Parameter validation: ensure all critical addresses are valid
        if (
            _owner == address(0) ||
            _token == address(0) ||
            _distributor == address(0)
        ) {
            revert ZeroAddress();
        }

        // Set immutable state variables
        owner = _owner;
        pai_token = _token;
        distributor = _distributor;

        // Initialize reentrancy lock to unlocked state
        _locked = _NOT_ENTERED;
    }

    // ========== Function Modifiers ==========

    /**
     * @notice Modifier to prevent reentrancy attacks
     * @dev Implements state machine pattern, more gas efficient than boolean
     *
     * Working principle:
     * 1. Check if already locked (_ENTERED state)
     * 2. Set to locked state
     * 3. Execute function body
     * 4. Restore to unlocked state
     *
     * Reverts:
     * - {Unauthorized} if reentrancy detected
     */
    modifier nonReentrant() {
        // Reentrancy detection
        if (_locked == _ENTERED) revert Unauthorized();

        // Lock
        _locked = _ENTERED;

        // Execute function body
        _;

        // Unlock
        _locked = _NOT_ENTERED;
    }

    /**
     * @notice Modifier to ensure vault has completed initialization
     * @dev Prevents unregistered vaults from being used
     *
     * Reverts:
     * - {Unauthorized} if vault is not initialized
     */
    modifier onlyInitialized() {
        if (!_initialized) revert Unauthorized();
        _;
    }

    // ========== Initialization Function ==========

    /**
     * @notice Complete vault initialization (final step of registration process)
     * @dev Called by distributor after successful vault registration, can only be called once
     *
     * Call timing:
     * - After user successfully registers via distributor.register_vault_with_signature()
     *
     * Access control:
     * - Can only be called by distributor contract
     * - Can only be called once
     *
     * State changes:
     * - Set _initialized to true
     *
     * Reverts:
     * - {Unauthorized} if caller is not distributor
     * - {AlreadyExists} if already initialized
     *
     * Emits:
     * - {Initialized} initialization completed
     */
    function complete_initialization() external {
        // Permission verification: only distributor can initialize
        if (msg.sender != distributor) revert Unauthorized();

        // State verification: prevent duplicate initialization
        if (_initialized) revert AlreadyExists();

        // Mark as initialized
        _initialized = true;

        // Emit initialization event
        emit Initialized(distributor);
    }

    // ========== Core Business Functions ==========

    /**
     * @notice User claims PAI allocation quota via signature
     * @dev Claims allocated PAI tokens from project vault to this vault
     *
     * Execution flow:
     * 1. Verify caller is owner
     * 2. Update state first (total_claimed)
     * 3. Call distributor to execute allocation
     * 4. If failed, rollback state and revert
     *
     * @param amount Claim amount (in wei)
     * @param deadline Signature expiration timestamp
     * @param signature EIP-712 signature data (65 bytes)
     *
     * Access control:
     * - Can only be called by vault owner
     * - Vault must be initialized
     *
     * Security measures:
     * - Reentrancy protection (nonReentrant)
     * - Checks-Effects-Interactions (CEI) pattern
     * - Rollback state update on failure
     *
     * Reverts:
     * - {Unauthorized} if caller is not owner or vault not initialized
     * - {TransferFailed} if allocation execution fails
     * - {SignatureExpired} if signature has expired
     * - {InvalidSignature} if signature verification fails
     *
     * Emits:
     * - {Claimed} claim successful
     */
    function claim_allocation(
        uint256 amount,
        uint256 deadline,
        bytes calldata signature
    ) external onlyInitialized nonReentrant {
        // Permission verification: only owner can claim
        if (msg.sender != owner) revert Unauthorized();

        // Effect: update state first (CEI pattern)
        // Use unchecked to save gas (overflow nearly impossible)
        unchecked {
            total_claimed += amount;
        }

        // Interaction: call external contract to execute allocation
        bool success = i_distributor(distributor).execute_allocation(
            amount,
            deadline,
            signature
        );

        // Check: verify execution result
        if (!success) {
            // If failed, rollback state update
            unchecked {
                total_claimed -= amount;
            }
            revert TransferFailed();
        }

        // Emit claim event
        emit Claimed(amount);
    }

    /**
     * @notice Withdraw PAI tokens from vault to owner wallet
     * @dev Transfers all PAI balance in vault to owner in one transaction
     *
     * Execution flow:
     * 1. Verify caller is owner
     * 2. Query current balance
     * 3. Update state first (total_withdrawn)
     * 4. Execute token transfer
     * 5. Verify transfer result
     *
     * Access control:
     * - Can only be called by vault owner
     * - Vault must be initialized
     *
     * Security measures:
     * - Reentrancy protection (nonReentrant)
     * - Checks-Effects-Interactions (CEI) pattern
     * - Reject execution if balance is zero
     *
     * Reverts:
     * - {Unauthorized} if caller is not owner or vault not initialized
     * - {ZeroAmount} if balance is zero
     * - {TransferFailed} if transfer fails
     *
     * Emits:
     * - {Withdrawn} withdrawal successful
     */
    function withdraw() external onlyInitialized nonReentrant {
        // Permission verification: only owner can withdraw
        if (msg.sender != owner) revert Unauthorized();

        // Check: query current balance
        uint256 balance = i_erc20(pai_token).balanceOf(address(this));
        if (balance == 0) revert ZeroAmount();

        // Effect: update state first (CEI pattern)
        unchecked {
            total_withdrawn += balance;
        }

        // Interaction: execute token transfer
        bool success = i_erc20(pai_token).transfer(owner, balance);
        if (!success) revert TransferFailed();

        // Emit withdrawal event
        emit Withdrawn(owner, balance);
    }

    /**
     * @notice Distributor-initiated reward distribution operation
     * @dev Handles third-party token rewards, transfers from awarder to accepter
     *
     * Execution flow:
     * 1. Verify caller is distributor
     * 2. Verify token address is valid and not PAI
     * 3. Get project vault address from distributor
     * 4. Get award role configuration from project vault
     * 5. Check awarder balance
     * 6. Execute transferFrom transfer
     *
     * @param token_address Reward token address to distribute
     *
     * Access control:
     * - Can only be called by distributor contract
     * - Vault must be initialized
     *
     * Restrictions:
     * - Cannot withdraw PAI tokens (avoid conflict with main business)
     * - Awarder balance must be greater than zero
     *
     * Reverts:
     * - {Unauthorized} if caller not distributor, vault not initialized, or attempting to withdraw PAI
     * - {ZeroAddress} if any critical address is zero
     * - {ZeroAmount} if reward balance is zero
     * - {TransferFailed} if transfer fails
     *
     * Emits:
     * - {AwardDistributed} reward distribution successful
     */
    function withdraw_award(address token_address) external onlyInitialized {
        // Permission verification: only distributor can initiate reward distribution
        if (msg.sender != distributor) revert Unauthorized();

        // Parameter validation: token address cannot be zero
        if (token_address == address(0)) revert ZeroAddress();

        // Business rule: prohibit withdrawing PAI tokens (avoid conflict with main business)
        if (token_address == pai_token) revert Unauthorized();

        // Get project vault address
        address project_vault = i_distributor(distributor).project_vault();
        if (project_vault == address(0)) revert ZeroAddress();

        // Get award role configuration (awarder and accepter)
        (address awarder, address accepter) = i_project_vault(project_vault)
            .get_award_roles(address(this));

        // Verify role addresses are valid
        if (awarder == address(0) || accepter == address(0)) {
            revert ZeroAddress();
        }

        // Check awarder's token balance
        uint256 amount = i_erc20(token_address).balanceOf(awarder);
        if (amount == 0) revert ZeroAmount();

        // Execute transfer from awarder to accepter
        // Note: This requires awarder to have pre-approved sufficient allowance to this contract
        bool success = i_erc20(token_address).transferFrom(
            awarder,
            accepter,
            amount
        );
        if (!success) revert TransferFailed();

        // Emit reward distribution event
        emit AwardDistributed(token_address, awarder, accepter, amount);
    }

    // ========== Query Functions ==========

    /**
     * @notice Query PAI token balance in vault
     * @dev Returns current amount of PAI tokens stored in vault
     *
     * @return balance PAI token balance (in wei)
     */
    function get_balance() external view returns (uint256) {
        return i_erc20(pai_token).balanceOf(address(this));
    }

    /**
     * @notice Query balance of specified token in vault
     * @dev Can be used to query reward token balance
     *
     * @param token_address Token contract address to query
     * @return balance Token balance (in wei)
     */
    function get_token_balance(
        address token_address
    ) external view returns (uint256) {
        return i_erc20(token_address).balanceOf(address(this));
    }

    /**
     * @notice Check if vault has completed initialization
     * @dev Used by frontend to verify vault status
     *
     * @return initialized Whether initialized
     */
    function is_initialized() external view returns (bool) {
        return _initialized;
    }

    // ========== Receive Functions ==========

    /**
     * @notice Reject direct ETH transfers
     * @dev Prevents accidental ETH sends to vault
     */
    receive() external payable {
        revert Unauthorized();
    }

    /**
     * @notice Reject all fallback calls
     * @dev Prevents unknown function calls
     */
    fallback() external payable {
        revert Unauthorized();
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200,
      "details": {
        "yul": true,
        "yulDetails": {
          "stackAllocation": true,
          "optimizerSteps": "dhfoDgvulfnTUtnIf"
        }
      }
    },
    "viaIR": false,
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "abi"
        ]
      }
    }
  }
}}

Tags:
Multisig, Multi-Signature, Factory|addr:0x336a96cfc59c985e77b3a0aa9b88327a6e917e91|verified:true|block:23632514|tx:0xf3f15eb7cfb44cb4e8180e1ecfc274d52a27e9b3411df488e2f648f61750c1d9|first_check:1761241165

Submitted on: 2025-10-23 19:39:28

Comments

Log in to comment.

No comments yet.