Skip to content

Commit

Permalink
Refactor SealedBidTokenSale contract to enhance documentation, improv…
Browse files Browse the repository at this point in the history
…e error messages, and clarify function descriptions for better code readability and maintainability.
  • Loading branch information
ylv-io committed Feb 3, 2025
1 parent 9ff3703 commit 57436e7
Showing 1 changed file with 84 additions and 47 deletions.
131 changes: 84 additions & 47 deletions src/apps/SealedBidTokenSale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,90 +9,107 @@ import {SafeERC20} from "@openzeppelin-5.0.1/contracts/token/ERC20/utils/SafeERC

/**
* @title SealedBidTokenSale
* @dev Sealed-bid auction-style token sale with USDC deposits and Merkle-based claims.
* Features time-bound participation, minimum/maximum caps, and non-custodial withdrawals.
* @notice Manages a sealed-bid token sale where users deposit USDC and receive tokens based on their allocations
* @dev Implements a non-custodial token sale mechanism with the following features:
* - Time-bound participation window
* - Minimum cap for sale success
* - USDC deposits from users
* - Merkle-based token allocation claims
* - Full refunds if minimum cap not reached
*/
contract SealedBidTokenSale is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;

/* ============ Custom Errors ============ */

/// @notice Invalid sale token address provided (zero address)
/// @notice Thrown when attempting to initialize with zero address for sale token
error InvalidSaleTokenAddress(address token);
/// @notice Invalid treasury address provided (zero address)
/// @notice Thrown when attempting to initialize with zero address for treasury
error InvalidTreasuryAddress(address treasury);
/// @notice Sale period has not started yet
/// @notice Thrown when attempting operations before sale start time
error SaleNotStarted(uint256 currentTime, uint256 startTime);
/// @notice Sale period has already ended
/// @notice Thrown when attempting operations after sale has ended
error SaleAlreadyEnded(uint256 currentTime);
/// @notice Sale period has not ended yet
/// @notice Thrown when attempting operations that require sale to be ended
error SaleNotEnded(uint256 currentTime);
/// @notice Operation requires capReached sale state
/// @notice Thrown when attempting operations that require minimum cap to be reached
error CapNotReached();
/// @notice Operation requires failed sale state
/// @notice Thrown when attempting withdrawals in successful sale
error SaleWasSuccessful();
/// @notice Deposited amount must be greater than zero
/// @notice Thrown when attempting to deposit zero amount
error ZeroDeposit();
/// @notice No funds available for withdrawal
/// @notice Thrown when user has no funds to withdraw
error NothingToWithdraw(address user);
/// @notice Tokens have already been claimed
/// @notice Thrown when attempting to claim tokens more than once
error AlreadyClaimed(address user);
/// @notice Provided Merkle proof is invalid
/// @notice Thrown when provided Merkle proof is invalid
error InvalidProof(bytes32[] proof, bytes32 leaf);
/// @notice Merkle root not set for claims
/// @notice Thrown when attempting claims before Merkle root is set
error MerkleRootNotSet();

/* ============ Events ============ */

/// @notice Emitted on USDC deposit
/// @notice Emitted when a user deposits USDC into the sale
/// @param user Address of the depositing user
/// @param amount Amount of USDC deposited
event Deposited(address indexed user, uint256 amount);
/// @notice Emitted on USDC withdrawal

/// @notice Emitted when a user withdraws USDC from a failed sale
/// @param user Address of the withdrawing user
/// @param amount Amount of USDC withdrawn
event Withdrawn(address indexed user, uint256 amount);
/// @notice Emitted when sale is ended

/// @notice Emitted when the sale is officially ended
/// @param capReached Whether the minimum cap was reached
/// @param totalDeposited Total amount of USDC deposited in sale
event SaleEnded(bool capReached, uint256 totalDeposited);
/// @notice Emitted when Merkle root is set

/// @notice Emitted when the Merkle root for token allocations is set
/// @param root New Merkle root value
event MerkleRootSet(bytes32 root);
/// @notice Emitted on capReached token claim

/// @notice Emitted when a user claims their allocated tokens
/// @param user Address of the claiming user
/// @param tokenAmount Amount of tokens claimed
event Claimed(address indexed user, uint256 tokenAmount);

/* ============ Immutable Parameters ============ */

/// @notice Sale token contract
/// @notice Token being sold in the sale
IERC20 public immutable saleToken;
/// @notice USDC token contract
/// @notice USDC token contract for deposits
IERC20 public immutable USDC;
/// @notice Treasury address for proceeds
/// @notice Address where sale proceeds will be sent
address public immutable treasury;
/// @notice Sale start timestamp
/// @notice Timestamp when the sale begins
uint256 public immutable startTime;
/// @notice Minimum USDC required for success
/// @notice Minimum amount of USDC required for sale success
uint256 public immutable minimumCap;

/* ============ State Variables ============ */

/// @notice Sale finalization status
/// @notice Whether the sale period has officially ended
bool public saleEnded;
/// @notice Sale success status
/// @notice Whether the minimum cap was reached by end of sale
bool public capReached;
/// @notice Total USDC deposited
/// @notice Running total of USDC deposited into sale
uint256 public totalDeposited;
/// @notice Merkle root for allocations
/// @notice Merkle root for verifying token allocations
bytes32 public merkleRoot;
/// @notice User deposits tracking
/// @notice Maps user addresses to their USDC deposit amounts
mapping(address => uint256) public deposits;
/// @notice Claims tracking
/// @notice Maps user addresses to whether they've claimed tokens
mapping(address => bool) public hasClaimed;

/* ============ Constructor ============ */

/**
* @notice Initialize sale parameters
* @param _saleToken Sale token address
* @param _treasury Treasury address for proceeds
* @param _usdcToken USDC token address
*
* @param _startTime Sale start timestamp
* @param _minimumCap Minimum USDC for success
* @notice Initializes the token sale with required parameters
* @param _saleToken Address of the token being sold
* @param _treasury Address where sale proceeds will be sent
* @param _usdcToken Address of the USDC token contract
* @param _startTime Timestamp when sale will begin
* @param _minimumCap Minimum USDC amount required for sale success
*/
constructor(address _saleToken, address _treasury, address _usdcToken, uint256 _startTime, uint256 _minimumCap)
Ownable(msg.sender)
Expand All @@ -110,7 +127,11 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
/* ============ User Functions ============ */

/**
* @notice Deposited USDC into the sale
* @notice Allows users to deposit USDC into the token sale
* @dev - Sale must be active (after start time, before end)
* - Amount must be greater than zero
* - Updates user's deposit balance and total deposits
* - Transfers USDC from user to contract
* @param amount Amount of USDC to deposit
*/
function deposit(uint256 amount) external nonReentrant {
Expand All @@ -127,7 +148,11 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
}

/**
* @notice Withdrawn USDC if sale failed
* @notice Allows users to withdraw their USDC if sale failed to reach minimum cap
* @dev - Sale must be ended and minimum cap not reached
* - User must have deposited USDC
* - Sends full deposit amount back to user
* - Sets user's deposit balance to zero
*/
function withdraw() external nonReentrant {
if (!saleEnded) revert SaleNotEnded(block.timestamp);
Expand All @@ -143,10 +168,15 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
}

/**
* @notice Claimed allocated tokens using Merkle proof
* @param saleTokenAllocation Token amount allocated to sender
* @param proof Merkle proof for allocation
* @param user User to claim tokens for
* @notice Allows users to claim their allocated tokens using a Merkle proof
* @dev - Sale must be ended successfully and Merkle root set
* - User must not have claimed already
* - Proof must be valid for user's allocation
* - Transfers allocated tokens and USDC to user
* @param saleTokenAllocation Amount of sale tokens allocated to user
* @param usdcAllocation Amount of USDC allocated to user
* @param proof Merkle proof verifying the allocation
* @param user Address of user claiming tokens
*/
function claimTokens(uint256 saleTokenAllocation, uint256 usdcAllocation, bytes32[] calldata proof, address user)
external
Expand Down Expand Up @@ -175,7 +205,10 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
/* ============ Admin Functions ============ */

/**
* @notice Ends sale
* @notice Allows owner to officially end the sale
* @dev - Can only be called once
* - Sets final sale status based on minimum cap
* - Emits event with final sale results
*/
function endSale() external onlyOwner {
if (saleEnded) revert SaleAlreadyEnded(block.timestamp);
Expand All @@ -187,8 +220,10 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
}

/**
* @notice Set Merkle root for allocations
* @param newRoot Root of allocation Merkle tree
* @notice Sets the Merkle root for verifying token allocations
* @dev - Sale must be ended successfully
* - Enables token claiming process
* @param newRoot The Merkle root hash of all valid allocations
*/
function setMerkleRoot(bytes32 newRoot) external onlyOwner {
if (!saleEnded || !capReached) revert CapNotReached();
Expand All @@ -197,7 +232,9 @@ contract SealedBidTokenSale is Ownable, ReentrancyGuard {
}

/**
* @notice Withdrawn proceeds to treasury
* @notice Allows owner to withdraw sale proceeds to treasury
* @dev - Sale must be ended successfully
* - Transfers all USDC to treasury address
*/
function withdrawProceeds() external onlyOwner {
if (!saleEnded || !capReached) revert CapNotReached();
Expand Down

0 comments on commit 57436e7

Please sign in to comment.