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.
}
}
}
"}}
Submitted on: 2025-11-01 10:37:15
Comments
Log in to comment.
No comments yet.