Crowdsale

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{"Crowdsale.sol":{"content":"// SPDX-License-Identifier: MIT\r
pragma solidity ^0.8.18;\r
\r
// These files are strictly provided as-is\r
// without any express or implied guarantees, representations, or warranties\r
// as to any of these files or any deployments hereof.\r
\r
// Any users of these files are doing so at their own risk.\r
\r
/// @notice immutable crowdsale contract for $ENSHROUD tokens, with ETH proceeds routed directly to the DAO treasury\r
/** @dev allows anyone to exchange ETH for $ENSHROUD tokens, with rates automatically incrementing in tiers.\r
 * each tier increment mints 1 million $ENSHROUD tokens to this contract for purchase, and rate doubles in each new tier -- no hard cap,\r
 * as tiers will eventually become economically impractical according to the price of ETH\r
 */\r
\r
// Solbase ReentrancyGuard (https://github.com/Sol-DAO/solbase/blob/main/src/utils/ReentrancyGuard.sol)\r
import {ReentrancyGuard} from "./ReentrancyGuard.sol";\r
\r
// Solbase SafeTransferLib (https://github.com/Sol-DAO/solbase/blob/main/src/utils/SafeTransferLib.sol)\r
import {SafeTransferLib} from "./SafeTransferLib.sol";\r
\r
/*///////////////////////////////////////////////////////////////\r
                            INTERFACES\r
//////////////////////////////////////////////////////////////*/\r
\r
interface IERC20_Crowdsale {\r
    /// @notice to mint $ENSHROUD tokens to this address automatically with each tier increment\r
    function mint(address to, uint256 value) external;\r
}\r
\r
contract Crowdsale is ReentrancyGuard {\r
    uint256 internal constant DECIMAL_PLACES = 1e18;\r
    uint256 internal minWei;\r
\r
    /// @notice token available for acquisition\r
    address public immutable enshroudToken;\r
\r
    /// @notice address where proceeds are routed\r
    address payable public immutable treasury;\r
\r
    bool internal initialised;\r
\r
    /// @notice counter for tiers\r
    uint256 public tierIndex;\r
\r
    /// @notice maps tierIndex to how much wei a buyer must send to receive one $ENSHROUD token in return\r
    mapping(uint256 =\u003e uint256) public rates;\r
\r
    /// @notice maps tierIndex to how many $ENSHROUD tokens remain in said tier (starting at 1 million per tier)\r
    mapping(uint256 =\u003e uint256) public tokensInTier;\r
\r
    /*///////////////////////////////////////////////////////////////\r
                            EVENTS\r
    //////////////////////////////////////////////////////////////*/\r
\r
    event TierSoldOut(uint256 tierIndex);\r
\r
    event TokenPurchase(\r
        address indexed purchaser,\r
        uint256 value,\r
        uint256 amount,\r
        uint256 remainingTokensInTier\r
    );\r
\r
    /*///////////////////////////////////////////////////////////////\r
                            ERRORS\r
    //////////////////////////////////////////////////////////////*/\r
\r
    error AlreadyInitialised();\r
    error CannotPurchaseAnEntireTier();\r
    error NotEnoughWei();\r
    error NotInitialised();\r
\r
    /*///////////////////////////////////////////////////////////////\r
                            FUNCTIONS\r
    //////////////////////////////////////////////////////////////*/\r
\r
    /// @param _initialRate initial tier rate of wei per $ENSHROUD token inclusive of decimals: for example for .01 ETH per token, pass 1e16\r
    /// @param _treasury immutable address where collected funds will be forwarded to\r
    /// @param _enshroudToken immutable address of the token available for acquisition\r
    /// @dev rates mapping commences at _initialRate; tierIndex starts at 0, which is mapped to the _initialRate\r
    constructor(\r
        uint256 _initialRate,\r
        address payable _treasury,\r
        address _enshroudToken\r
    ) payable {\r
        treasury = _treasury;\r
        enshroudToken = _enshroudToken;\r
        rates[0] = _initialRate;\r
        minWei = _initialRate;\r
    }\r
\r
    /**\r
     * @dev calls \u0027buyTokens()\u0027 passing msg.value to ultimately buy $ENSHROUD tokens.\r
     * \u0027minWei\u0027 corresponds to the minimum amount of wei required for \u0027buyTokens()\u0027 to not revert.\r
     */\r
    receive() external payable {\r
        if (!initialised) revert NotInitialised();\r
        // msg.value will be preserved, so \u0027buyTokens()\u0027 will revert if msg.value is below minWei\r
        buyTokens(msg.sender);\r
    }\r
\r
    /// @notice initialise after a minter passes address(this) to updateMinterStatus() in the Enshroud Token contract\r
    /// @dev only callable once due to \u0027initialised\u0027 check\r
    function initialise() external {\r
        if (initialised) revert AlreadyInitialised();\r
        initialised = true;\r
        tokensInTier[0] = 1e6 * DECIMAL_PLACES;\r
        unchecked {\r
            IERC20_Crowdsale(enshroudToken).mint(\r
                address(this),\r
                1e6 * DECIMAL_PLACES\r
            );\r
        }\r
    }\r
\r
    /// @notice to purchase $ENSHROUD tokens: purchaser pays by sending ETH as msg.value calling this function or directly to this contract address to be converted in receive()\r
    /// @param _purchaser address of purchaser (msg.sender if calling this function directly, or address which sent ETH to the receive() function)\r
    function buyTokens(address _purchaser) public payable nonReentrant {\r
        if (!initialised) revert NotInitialised();\r
        if (msg.value \u003c= minWei) revert NotEnoughWei();\r
\r
        // either the receive() function is calling this method, or msg.sender chose a different address to receive the $ENSHROUD tokens\r
\r
        // calculate token amount, by number of wei per $ENSHROUD token for applicable tier\r
        uint256 _purchaseAmount = (msg.value / rates[tierIndex]) *\r
			DECIMAL_PLACES;\r
        uint256 _tokensInTier = tokensInTier[tierIndex];\r
\r
        // send msg.value to treasury\r
        SafeTransferLib.safeTransferETH(treasury, msg.value);\r
\r
        if (_purchaseAmount \u003e _tokensInTier) {\r
            // if \u0027_purchaseAmount\u0027 is more than the number of $ENSHROUD tokens remaining in this tier\r
			// tokens bought from next Tier will be half the surplus, because\r
			// the next Tier\u0027s price will be 2X that of the current Tier\r
			uint256 _tokensInNextTier = (_purchaseAmount - _tokensInTier) / 2;\r
\r
			// make the switch to new Tier\r
            delete tokensInTier[tierIndex]; // tier now empty\r
            _incrementTier();\r
\r
            // revert if purchase would clear an entire tier in one transaction (1 million $ENSHROUD tokens); purchaser should use more than one transaction if so desired.\r
            if (_tokensInNextTier \u003e= tokensInTier[tierIndex])\r
                revert CannotPurchaseAnEntireTier();\r
\r
            unchecked {\r
                tokensInTier[tierIndex] -= _tokensInNextTier; // cannot underflow due to prior condition check\r
\r
                // deliver $ENSHROUD tokens to \u0027_purchaser\u0027\r
                SafeTransferLib.safeTransfer(\r
                    enshroudToken,\r
                    _purchaser,\r
                    _tokensInTier + _tokensInNextTier\r
                );\r
\r
                emit TierSoldOut(tierIndex); // these events are 1-based\r
                emit TokenPurchase(\r
                    _purchaser,\r
                    msg.value,\r
                    _tokensInTier + _tokensInNextTier,\r
                    tokensInTier[tierIndex]\r
                );\r
            }\r
        } else if (_purchaseAmount == _tokensInTier) {\r
            // if \u0027_purchaseAmount\u0027 exactly equals the number of $ENSHROUD tokens remaining in this tier\r
            // current tier now empty; increment tierIndex and deliver tokens\r
            delete tokensInTier[tierIndex];\r
\r
            _incrementTier();\r
\r
            // deliver $ENSHROUD tokens to \u0027_purchaser\u0027\r
            SafeTransferLib.safeTransfer(\r
                enshroudToken,\r
                _purchaser,\r
                _purchaseAmount\r
            );\r
\r
            emit TierSoldOut(tierIndex);\r
            emit TokenPurchase(\r
                _purchaser,\r
                msg.value,\r
                _purchaseAmount,\r
                tokensInTier[tierIndex]\r
            );\r
        } else {\r
            // if \u0027_purchaseAmount\u0027 does not empty tier\r
            tokensInTier[tierIndex] -= _purchaseAmount;\r
\r
            // deliver $ENSHROUD tokens to \u0027_purchaser\u0027\r
            SafeTransferLib.safeTransfer(\r
                enshroudToken,\r
                _purchaser,\r
                _purchaseAmount\r
            );\r
\r
            emit TokenPurchase(\r
                _purchaser,\r
                msg.value,\r
                _purchaseAmount,\r
                tokensInTier[tierIndex]\r
            );\r
        }\r
    }\r
\r
    function _incrementTier() internal {\r
        unchecked {\r
            // increment tier, will not overflow\r
            ++tierIndex;\r
\r
            // new tier rate is double the prior tier\u0027s rate; will not overflow until an economically impractical tier, nor underflow due to prior incrementation\r
            rates[tierIndex] = rates[tierIndex - 1] * 2;\r
\r
			// minWei likewise doubles\r
			minWei *= 2;\r
\r
            // fixed value of 1 million including decimals, will not overflow\r
            tokensInTier[tierIndex] = 1e6 * DECIMAL_PLACES;\r
\r
            // mint 1 million $ENSHROUD tokens to this address for the next tier, fixed amount will not overflow\r
            IERC20_Crowdsale(enshroudToken).mint(\r
                address(this),\r
                1e6 * DECIMAL_PLACES\r
            );\r
        }\r
    }\r
}\r
"},"ReentrancyGuard.sol":{"content":"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Gas-optimized reentrancy protection for smart contracts.
/// @author Solbase (https://github.com/Sol-DAO/solbase/blob/main/src/utils/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    error Reentrancy();

    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        if (locked == 2) revert Reentrancy();

        locked = 2;

        _;

        locked = 1;
    }
}
"},"SafeTransferLib.sol":{"content":"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author SolDAO (https://github.com/Sol-DAO/solbase/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won\u0027t check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
    /// -----------------------------------------------------------------------
    /// Custom Errors
    /// -----------------------------------------------------------------------

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// -----------------------------------------------------------------------
    /// ETH Operations
    /// -----------------------------------------------------------------------

    /// @dev Sends `amount` (in wei) ETH to `to`.
    /// Reverts upon failure.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// -----------------------------------------------------------------------
    /// ERC20 Operations
    /// -----------------------------------------------------------------------

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // We\u0027ll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0x00, 0x095ea7b3)
            mstore(0x20, to) // Append the "to" argument.
            mstore(0x40, amount) // Append the "amount" argument.

            if iszero(
                and(
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can\u0027t just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    // We use 0x44 because that\u0027s the total length of our calldata (0x04 + 0x20 * 2)
                    // Counterintuitively, this call() must be positioned after the or() in the
                    // surrounding and() because and() evaluates its arguments from right to left.
                    call(gas(), token, 0, 0x1c, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `ApproveFailed()`.
                mstore(0x00, 0x3e3f8f73)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x40, memPointer) // Restore the memPointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // We\u0027ll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0x00, 0xa9059cbb)
            mstore(0x20, to) // Append the "to" argument.
            mstore(0x40, amount) // Append the "amount" argument.

            if iszero(
                and(
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can\u0027t just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    // We use 0x44 because that\u0027s the total length of our calldata (0x04 + 0x20 * 2)
                    // Counterintuitively, this call() must be positioned after the or() in the
                    // surrounding and() because and() evaluates its arguments from right to left.
                    call(gas(), token, 0, 0x1c, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x40, memPointer) // Restore the memPointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // We\u0027ll write our calldata to this slot below, but restore it later.
            let memPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(0x00, 0x23b872dd)
            mstore(0x20, from) // Append the "from" argument.
            mstore(0x40, to) // Append the "to" argument.
            mstore(0x60, amount) // Append the "amount" argument.

            if iszero(
                and(
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can\u0027t just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    // We use 0x64 because that\u0027s the total length of our calldata (0x04 + 0x20 * 3)
                    // Counterintuitively, this call() must be positioned after the or() in the
                    // surrounding and() because and() evaluates its arguments from right to left.
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, memPointer) // Restore the memPointer.
        }
    }
}
"}}

Tags:
Proxy, Upgradeable, Factory|addr:0xb8b391b0fe98aa0246a9907f0052b69bd8ffa5e5|verified:true|block:23700205|tx:0x2bd1d85f4fa60702b588d2b0f5a1cb8593d339f4597d50898dccbafd5e8bd233|first_check:1761989835

Submitted on: 2025-11-01 10:37:15

Comments

Log in to comment.

No comments yet.