LogoLogo
  • Introduction
    • Overview
    • Concepts
      • Decentralised Exchange (DEX)
      • Liquidity Pools
      • Swaps
      • Automated Market Maker (AMM)
      • Fees
  • Developers
    • Smart Contracts
      • Error Codes
      • Flash Loans
      • Internal User Balances
      • Pool Interface
    • Joins and Exits
      • Pool Joins
      • Pool Exits
    • Maths
      • Weighted Maths
    • Arbitrage System
      • Arbitrage Functions
    • Swaps
      • Single Swap
      • Batch Swaps
    • Arbitrage Profit Distributor
  • Resources
    • Code Licensing
    • Change Log
    • Glossary
    • Web3
    • Security & Audits
Powered by GitBook
On this page
  • Overview
  • State Variables
  • Events
  • Roles & Permissions
  • Lifecycle of a Distribution
  • Key Functions
  • How to Claim (User Guide)
  • Best Practices & Security Notes
  1. Developers

Arbitrage Profit Distributor

Overview

The ProfitDistribution contract manages the distribution of arbitrage profits to Liquidity Providers (LPs). It utilizes a Merkle Tree–based approach, allowing users to claim their portion of profits in a gas-efficient and trust-minimized manner. Once a new Merkle Root is published (representing a new distribution epoch), eligible users can submit proofs to claim their tokens.

Key features:

  • Merkle Tree verification to prove a user’s allocation without storing large amounts of on-chain data.

  • Permissioned updates to the Merkle Root via a rootSetter.

  • Pause / Unpause functionality to safely update distribution epochs.

  • Emergency Withdraw mechanism to handle unexpected events.

  • Stop / Resume functionality (isStopped) to disable the contract entirely if needed.

State Variables

solidityCopybytes32 public merkleRoot;
address public rootSetter;
bool public isStopped;
mapping(bytes32 => mapping(bytes32 => bool)) public isClaimed;
mapping(bytes32 => mapping(address => bool)) public isAnyPoolClaimed;
mapping(bytes32 => mapping(address => bool)) public isAllPoolsClaimed;
  • merkleRoot The current root of the Merkle Tree. Each root corresponds to a distribution “epoch.”

  • rootSetter The address permitted to update the Merkle Root and pause/unpause the contract.

  • isStopped A global kill-switch. When true, most functionality is disabled (cannot pause/unpause or process claims).

  • isClaimed[_merkleRoot][leaf] Tracks whether a specific “leaf” (i.e., a unique claim) has already been claimed under a particular Merkle Root.

  • isAnyPoolClaimed[_merkleRoot][user] and isAllPoolsClaimed[_merkleRoot][user]

    • For convenience, the contract allows two modes of claiming:

      1. Single-Pool Claim: User claims rewards for a single pool (i.e., pool != address(0)).

      2. All-Pools Claim: User claims all rewards across multiple pools (i.e., pool == address(0)).

    • These mappings prevent double-claiming in each mode.

Events

solidityCopyevent Claimed(address indexed user, address[] indexed tokens, uint256[] amounts);
event EmergencyWihdraw(address toAddress);
  • Claimed Emitted whenever a user successfully claims tokens.

    • user: The address that claimed the distribution.

    • tokens: Array of ERC20 tokens claimed.

    • amounts: Array of amounts corresponding to each token.

  • EmergencyWihdraw Emitted when the contract owner withdraws all tokens to an emergency address (the owner itself) due to a critical situation.

Roles & Permissions

  1. Owner

    • Has full control to:

      • Set a new rootSetter via setRootSetter(address).

      • Stop/resume the contract via changeContractState(bool).

      • Perform emergency withdrawals.

    • Cannot directly update the Merkle Root (this is reserved for rootSetter).

  2. Root Setter

    • Designated via rootSetter address.

    • Can pause/unpause the contract.

    • Can set a new Merkle Root during the paused state.

  3. Anyone

    • Can claim tokens if they have a valid Merkle proof for the current Merkle Root and have not already claimed.

Lifecycle of a Distribution

  1. Root Setter Pauses the Contract

    • Calls pause() when the contract is not stopped and currently unpaused.

    • Ensures no claims occur during the root update.

  2. Root Setter Sets the Merkle Root

    • Calls setMerkleRoot(bytes32 newMerkleRoot) while the contract is paused (and not stopped).

    • Updates merkleRoot to the new value representing the latest distribution data (i.e., the set of user allocations).

    • Automatically unpauses the contract upon success.

  3. Users Claim Rewards

    • While the contract is unpaused, users call claim() with:

      • A valid Merkle proof (bytes32[] memory proof).

      • The pool address for which they are claiming (or address(0) to claim from all pools).

      • Arrays of tokens and corresponding amounts.

    • The contract verifies the proof against the current merkleRoot.

    • If valid and not already claimed, tokens are transferred to the user.

  4. Repeat

    • For each new distribution epoch, the Root Setter can repeat steps (1) and (2) with an updated Merkle Root.

Key Functions

claim(...)

solidityCopyfunction claim(
    bytes32[] memory proof,
    address pool,
    address[] memory tokens,
    uint256[] memory amounts
)
    external
    nonReentrant
    whenNotPaused
    notStopped
{
    ...
}

Purpose Allows a user to claim their share of tokens for a particular pool (or for all pools at once) by providing a valid Merkle proof.

Parameters

  • proof: The Merkle proof verifying that the user is indeed entitled to the specified tokens and amounts.

  • pool: The pool address for which the user is claiming. If pool == address(0), the user claims from all pools at once.

  • tokens: An array of ERC20 tokens that the user is claiming.

  • amounts: An array of token amounts corresponding to tokens.

Verification Logic

  1. Leaf Construction:

    solidityCopybytes32 leaf = keccak256(
        abi.encodePacked(keccak256(abi.encode(msg.sender, pool, tokens, amounts)))
    );
  2. Proof Verification: Uses MerkleProof.verify(proof, merkleRoot, leaf) to confirm that leaf is included in the current merkleRoot.

  3. Double-Claim Prevention:

    • Checks isClaimed[merkleRoot][leaf] to ensure the exact claim has not been used.

    • If pool == address(0), checks isAnyPoolClaimed[merkleRoot][msg.sender].

    • Otherwise, checks isAllPoolsClaimed[merkleRoot][msg.sender].

  4. Transfers: Iterates over tokens and transfers each corresponding amount to the caller (msg.sender) using SafeERC20.

  5. Record & Emit:

    • Marks the claim as used.

    • Emits Claimed(msg.sender, tokens, amounts) event.

Reverts if

  • Proof is invalid (MerkleProof.verify(...) fails).

  • Claim was already processed for this merkleRoot.

  • The contract is paused or stopped.

setMerkleRoot(bytes32 newMerkleRoot)

solidityCopyfunction setMerkleRoot(bytes32 newMerkleRoot)
    external
    onlyRootSetter
    whenPaused
    notStopped
{
    merkleRoot = newMerkleRoot;
    _unpause();
}

Purpose Updates the contract to use a new Merkle Root for distribution. This should be called after computing the new distribution off-chain.

Flow

  1. Pause Requirement: Can only be called when the contract is paused.

  2. Update Root: Sets merkleRoot to the new value.

  3. Unpause: Automatically calls _unpause() to allow users to begin claiming.

pause() / unpause()

solidityCopyfunction pause() external onlyRootSetter whenNotPaused notStopped { ... }
function unpause() external onlyRootSetter whenPaused notStopped { ... }

Purpose

  • pause(): Temporarily disables claiming. Used before updating the Merkle Root.

  • unpause(): Re-enables claiming after a successful root update.

changeContractState(bool status)

solidityCopyfunction changeContractState(bool status) external onlyOwner {
    isStopped = status;
}

Purpose Sets isStopped to true or false. When isStopped == true, the contract cannot be paused/unpaused or used for claims. Essentially a higher-level emergency stop.

emergencyWithdraw(address[] memory tokens)

solidityCopyfunction emergencyWithdraw(address[] memory tokens)
    external
    onlyOwner
    notStopped
{
    ...
}

Purpose Allows the owner to withdraw all of a specified set of ERC20 tokens from the contract to the owner’s address. Intended for catastrophic scenarios or contract migrations.


How to Claim (User Guide)

  1. Obtain Proof Off-Chain

    • The distribution operator (or your front-end) calculates the Merkle Tree of the distribution and provides each user with a Merkle proof for their claim.

    • This proof, along with the user’s intended pool address, token list, and amounts, is provided to the user.

  2. Call claim()

    • Construct the transaction parameters:

      • proof: The array of bytes32 from the off-chain aggregator.

      • pool: The specific pool address or address(0) to claim from all pools.

      • tokens and amounts: The exact tokens and amounts you’re entitled to under the distribution.

    • Submit the transaction to the ProfitDistribution contract.

  3. Receive Tokens

    • If the proof is valid and you have not already claimed, the contract transfers the specified tokens to your wallet.

    • The contract records that your claim is used, preventing double-claiming.


Best Practices & Security Notes

  1. Root Updates

    • Always pause the contract before setting a new merkleRoot.

    • Double-check the distribution data off-chain before finalizing.

  2. Double-Claim Prevention

    • The contract enforces it on-chain via isClaimed, isAnyPoolClaimed, and isAllPoolsClaimed.

  3. Stopping vs. Pausing

    • Pausing is used for routine Merkle Root updates.

    • Stopping (isStopped) is an irreversible (or at least heavier) measure that prevents further operations. It should only be used in severe cases.

  4. Emergency Withdraw

    • Use only if a major exploit or migration scenario arises. This function will move all tokens to the owner’s address.

  5. Ownership & Root Setter

    • The owner can replace the rootSetter, but the owner cannot directly set the Merkle Root. This separation of roles ensures safer operational practices (e.g., a specialized distribution manager account or script can handle the root updates).


PreviousBatch SwapsNextCode Licensing

Last updated 2 months ago