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
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. Whentrue
, 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]
andisAllPoolsClaimed[_merkleRoot][user]
For convenience, the contract allows two modes of claiming:
Single-Pool Claim: User claims rewards for a single pool (i.e.,
pool != address(0)
).All-Pools Claim: User claims all rewards across multiple pools (i.e.,
pool == address(0)
).
These mappings prevent double-claiming in each mode.
Events
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
Owner
Has full control to:
Set a new
rootSetter
viasetRootSetter(address)
.Stop/resume the contract via
changeContractState(bool)
.Perform emergency withdrawals.
Cannot directly update the Merkle Root (this is reserved for
rootSetter
).
Root Setter
Designated via
rootSetter
address.Can pause/unpause the contract.
Can set a new Merkle Root during the paused state.
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
Root Setter Pauses the Contract
Calls
pause()
when the contract is not stopped and currently unpaused.Ensures no claims occur during the root update.
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.
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 (oraddress(0)
to claim from all pools).Arrays of
tokens
and correspondingamounts
.
The contract verifies the proof against the current
merkleRoot
.If valid and not already claimed, tokens are transferred to the user.
Repeat
For each new distribution epoch, the Root Setter can repeat steps (1) and (2) with an updated Merkle Root.
Key Functions
claim(...)
claim(...)
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 specifiedtokens
andamounts
.pool
: The pool address for which the user is claiming. Ifpool == 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 totokens
.
Verification Logic
Leaf Construction:
Proof Verification: Uses
MerkleProof.verify(proof, merkleRoot, leaf)
to confirm thatleaf
is included in the currentmerkleRoot
.Double-Claim Prevention:
Checks
isClaimed[merkleRoot][leaf]
to ensure the exact claim has not been used.If
pool == address(0)
, checksisAnyPoolClaimed[merkleRoot][msg.sender]
.Otherwise, checks
isAllPoolsClaimed[merkleRoot][msg.sender]
.
Transfers: Iterates over
tokens
and transfers each correspondingamount
to the caller (msg.sender
) usingSafeERC20
.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)
setMerkleRoot(bytes32 newMerkleRoot)
Purpose Updates the contract to use a new Merkle Root for distribution. This should be called after computing the new distribution off-chain.
Flow
Pause Requirement: Can only be called when the contract is paused.
Update Root: Sets
merkleRoot
to the new value.Unpause: Automatically calls
_unpause()
to allow users to begin claiming.
pause()
/ unpause()
pause()
/ unpause()
Purpose
pause()
: Temporarily disables claiming. Used before updating the Merkle Root.unpause()
: Re-enables claiming after a successful root update.
changeContractState(bool status)
changeContractState(bool 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)
emergencyWithdraw(address[] memory tokens)
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)
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.
Call
claim()
Construct the transaction parameters:
proof
: The array ofbytes32
from the off-chain aggregator.pool
: The specific pool address oraddress(0)
to claim from all pools.tokens
andamounts
: The exact tokens and amounts you’re entitled to under the distribution.
Submit the transaction to the ProfitDistribution contract.
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
Root Updates
Always pause the contract before setting a new
merkleRoot
.Double-check the distribution data off-chain before finalizing.
Double-Claim Prevention
The contract enforces it on-chain via
isClaimed
,isAnyPoolClaimed
, andisAllPoolsClaimed
.
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.
Emergency Withdraw
Use only if a major exploit or migration scenario arises. This function will move all tokens to the owner’s address.
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).
Last updated