Skip to content

Commit

Permalink
SealedBidTokenSale (#338)
Browse files Browse the repository at this point in the history
* Add SealedBidTokenSale contract and specification for a sealed-bid, multi-unit uniform-price token sale with USDC deposits, using OpenZeppelin's Ownable for ownership, Merkle-based token claims, and proceeds withdrawal to a treasury address.

* Remove outdated ownership and treasury details from SealedBidTokenSale specification for clarity.

* Remove maximum cap check and associated error handling from SealedBidTokenSale contract.

* Refactor SealedBidTokenSale contract: update OpenZeppelin imports, use SafeERC20 for USDC transfers, and rename usdcToken to USDC for clarity.

* Refactor SealedBidTokenSale constructor to use custom errors for invalid treasury address and end

* Refactor SealedBidTokenSale contract: enhance error handling, update event emissions, improve function documentation, and streamline token transfer logic.

* Remove unnecessary whitespace in SealedBidTokenSale.sol for improved code readability.

* Add sale token address validation and initialization in SealedBidTokenSale contract.

* Refactor SealedBidTokenSale contract to enhance error handling with detailed InvalidProof error and update finalize logic; add comprehensive unit tests for contract functionality.

* Refactor SealedBidTokenSale test contract: rename file, update Merkle proof logic, and enhance test coverage.

* Refactor SealedBidTokenSale contract: remove endTime, rename functions and events for clarity, update tests accordingly.

* Remove maximumCap from SealedBidTokenSale contract and update related tests and constructor parameters.

* Refactor SealedBidTokenSale contract and test to include USDC allocation in claimTokens function, update Merkle proof logic, and adjust test setup and assertions accordingly.

* Refactor BridgerL2.t.sol by moving 'deal' function call for gasFee after setting bridgeData.gasFee.

* Refactor SealedBidTokenSale contract to replace 'successful' with 'capReached' for clarity in sale status logic.

* Refactor SealedBidTokenSale contract to include user address in claimTokens function and update Merkle proof handling in tests.

* Refactor error handling in SealedBidTokenSale contract and enhance unit tests with additional scenarios for deposit, endSale, withdraw, claimTokens, setMerkleRoot, and withdrawProceeds functions.

* Refactor SealedBidTokenSale contract to enhance documentation, improve error messages, and clarify function descriptions for better code readability and maintainability.

* Refactor error handling and add comments for clarity in SealedBidTokenSale contract and update corresponding test case.

* Refactor SealedBidTokenSale: simplify timing, remove max cap, add dual allocations, optimize gas, and enhance security with reentrancy protection.

* Removed the "Key Design Changes" section from SealedBidTokenSale.md, simplifying the technical specification and focusing on the core contract details.

* Add SaleInfo struct and saleStatus function to SealedBidTokenSale contract, update state variables, and enhance unit tests for comprehensive sale status tracking.

* Add maxPrice functionality to SealedBidTokenSale contract and update tests accordingly.

* Refactor deposit logic to enforce a minimum deposit amount and update corresponding tests.

* Add max price range check in SealedBidTokenSale and update unit tests accordingly.

* Add early participation window for emissaries in SealedBidTokenSale contract with tests for deposit logic and boundary conditions.

* Refactor SealedBidTokenSale contract to support UUPS upgradeability, update imports, and adjust unit tests for proxy initialization.

* Add SealedBidTokenSale deployment script, update artifacts with new contract addresses, and include transaction details in JSON files for chain 7887.
  • Loading branch information
ylv-io authored Feb 9, 2025
1 parent aaf4214 commit cd07409
Show file tree
Hide file tree
Showing 8 changed files with 2,732 additions and 2 deletions.
302 changes: 302 additions & 0 deletions broadcast/155-deploy-sale.s.sol/7887/run-1739030918.json

Large diffs are not rendered by default.

302 changes: 302 additions & 0 deletions broadcast/155-deploy-sale.s.sol/7887/run-latest.json

Large diffs are not rendered by default.

71 changes: 71 additions & 0 deletions script/migrations/155-deploy-sale.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

import {SealedBidTokenSale} from "@kinto-core/apps/SealedBidTokenSale.sol";

import {SafeBeaconProxy} from "@kinto-core/proxy/SafeBeaconProxy.sol";
import {KintoAppRegistry} from "@kinto-core/apps/KintoAppRegistry.sol";

import {UUPSProxy} from "@kinto-core-test/helpers/UUPSProxy.sol";
import {MigrationHelper} from "@kinto-core-script/utils/MigrationHelper.sol";

import "@kinto-core-test/helpers/ArrayHelpers.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

contract DeployScript is Script, MigrationHelper {
using ArrayHelpers for *;

address public constant SOCKET_APP = 0x3e9727470C66B1e77034590926CDe0242B5A3dCc;
address public constant KINTO = 0x010700808D59d2bb92257fCafACfe8e5bFF7aB87;
address public constant TREASURY = 0x793500709506652Fcc61F0d2D0fDa605638D4293;
address public constant USDC = 0x05DC0010C9902EcF6CBc921c6A4bd971c69E5A2E;
//Tuesday, Feb 11, 2025, 10:00:00 AM PT.
uint256 public constant PRE_START_TIME = 1739296800;
//Tuesday, Feb 18, 2025, 10:00:00 AM PT.
uint256 public constant START_TIME = 1739901600;
uint256 public constant MINIMUM_CAP = 250_000 * 1e6;

function run() public override {
super.run();

if (_getChainDeployment("SealedBidTokenSale") != address(0)) {
console2.log("SealedBidTokenSale is deployed");
return;
}

vm.broadcast(deployerPrivateKey);
SealedBidTokenSale impl = new SealedBidTokenSale(KINTO, TREASURY, USDC, PRE_START_TIME, START_TIME, MINIMUM_CAP);

(bytes32 salt, address expectedAddress) =
mineSalt(keccak256(abi.encodePacked(type(UUPSProxy).creationCode, abi.encode(address(impl), ""))), "5A1E00");

vm.broadcast(deployerPrivateKey);
UUPSProxy proxy = new UUPSProxy{salt: salt}(address(impl), "");
SealedBidTokenSale sale = SealedBidTokenSale(address(proxy));

_handleOps(
abi.encodeWithSelector(
KintoAppRegistry.addAppContracts.selector, SOCKET_APP, [address(sale)].toMemoryArray()
),
address(_getChainDeployment("KintoAppRegistry"))
);

uint256[] memory privateKeys = new uint256[](1);
privateKeys[0] = deployerPrivateKey;
_handleOps(
abi.encodeWithSelector(SealedBidTokenSale.initialize.selector),
payable(kintoAdminWallet),
address(proxy),
0,
address(0),
privateKeys
);

assertEq(address(sale), address(expectedAddress));
assertEq(address(sale.USDC()), USDC);

saveContractAddress("SealedBidTokenSale", address(sale));
saveContractAddress("SealedBidTokenSale-impl", address(impl));
}
}
164 changes: 164 additions & 0 deletions src/apps/SealedBidTokenSale.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
# SealedBidTokenSale Technical Specification

Below is the **technical specification** for a Solidity **sealed‐bid** token sale contract.

---

## 1. **Contract Overview**

- **Name**: `SealedBidTokenSale`
- **Purpose**: Accept USDC deposits for a token sale, enforce timing and minimum cap requirements, enable refunds if the sale is unsuccessful, and distribute tokens and USDC allocations via a Merkle proof if successful.
- **Inheritance**: `Ownable`, `ReentrancyGuard`

---

## 2. **Key Roles**

- **Owner**:
- Inherits from OpenZeppelin `Ownable`.
- Sets crucial parameters during contract deployment.
- Controls sale finalization, Merkle root setting, and proceeds withdrawal.

- **Participants**:
- Deposit USDC during the sale window.
- Withdraw their deposit if the sale fails.
- Claim tokens and USDC allocations after sale success using a Merkle proof.

- **Treasury**:
- Immutable address specified at deployment.
- Receives USDC proceeds upon successful sale completion.

---

## 3. **Immutable Parameters**

1. **`saleToken`** (`IERC20`)
- Token being sold through the contract.
- Set at construction.

2. **`USDC`** (`IERC20`)
- USDC token contract reference for deposits.
- Set at construction.

3. **`treasury`** (`address`)
- Fixed address that receives proceeds.
- Set at construction.

4. **`startTime`** (`uint256`)
- Sale start timestamp.
- Set at construction.

5. **`minimumCap`** (`uint256`)
- Minimum USDC required for success.
- Set at construction.

---

## 4. **State Variables**

1. **`saleEnded`** (`bool`)
- Indicates if owner has ended the sale.

2. **`capReached`** (`bool`)
- Set to `true` if `totalDeposited >= minimumCap` when sale ends.

3. **`totalDeposited`** (`uint256`)
- Sum of all USDC deposits.

4. **`merkleRoot`** (`bytes32`)
- Root hash for token and USDC allocation proofs.

5. **`deposits`** (`mapping(address => uint256)`)
- Tracks each user's USDC deposit amount.

6. **`hasClaimed`** (`mapping(address => bool)`)
- Records whether an address has claimed their allocation.

---

## 5. **Core Functions**

### 5.1 **`deposit(uint256 amount)`**
- **Purpose**: Accepts USDC deposits from participants.
- **Constraints**:
1. Must be after `startTime`.
2. Sale must not be ended.
3. Amount must be non-zero.
- **Effects**:
- Updates `deposits[msg.sender]` and `totalDeposited`.
- Transfers USDC from sender to contract.
- Emits `Deposited` event.

### 5.2 **`withdraw()`**
- **Purpose**: Returns USDC to depositors if sale fails.
- **Constraints**:
1. Sale must be ended.
2. Cap must not be reached.
3. Caller must have non-zero deposit.
- **Effects**:
- Returns user's entire USDC deposit.
- Zeroes their deposit balance.
- Emits `Withdrawn` event.

### 5.3 **`endSale()`** (Owner-only)
- **Purpose**: Finalizes sale and determines success.
- **Constraints**:
1. Only callable by owner.
2. Sale must not already be ended.
- **Effects**:
- Sets `saleEnded = true`.
- Sets `capReached` based on minimum cap check.
- Emits `SaleEnded` event.

### 5.4 **`claimTokens(uint256 saleTokenAllocation, uint256 usdcAllocation, bytes32[] calldata proof, address user)`**
- **Purpose**: Processes token and USDC claims using Merkle proofs.
- **Constraints**:
1. Sale must be ended and successful.
2. Merkle root must be set.
3. User must not have claimed.
4. Valid Merkle proof required.
- **Effects**:
- Marks user as claimed.
- Transfers allocated sale tokens.
- Returns allocated USDC.
- Emits `Claimed` event.

### 5.5 **`setMerkleRoot(bytes32 newRoot)`** (Owner-only)
- **Purpose**: Sets allocation Merkle root.
- **Constraints**:
1. Sale must be ended and successful.
2. Only callable by owner.
- **Effects**:
- Sets `merkleRoot`.
- Emits `MerkleRootSet` event.

### 5.6 **`withdrawProceeds()`** (Owner-only)
- **Purpose**: Sends USDC to treasury.
- **Constraints**:
1. Sale must be ended and successful.
2. Only callable by owner.
- **Effects**:
- Transfers all USDC to treasury address.

---

## 6. **Custom Errors**

1. **Parameter Validation**:
- `InvalidSaleTokenAddress`
- `InvalidTreasuryAddress`
- `ZeroDeposit`

2. **State Checks**:
- `SaleNotStarted`
- `SaleAlreadyEnded`
- `SaleNotEnded`
- `CapNotReached`
- `SaleWasSuccessful`

3. **Claim Validation**:
- `NothingToWithdraw`
- `AlreadyClaimed`
- `InvalidProof`
- `MerkleRootNotSet`

Loading

0 comments on commit cd07409

Please sign in to comment.