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"
]
}
}
}
}}
Submitted on: 2025-10-24 12:27:48
Comments
Log in to comment.
No comments yet.