Skip to content

Instantly share code, notes, and snippets.

@WPSmartContracts
Created September 17, 2024 04:17
Show Gist options
  • Select an option

  • Save WPSmartContracts/01c28acab1b91219c811aeb524dcc529 to your computer and use it in GitHub Desktop.

Select an option

Save WPSmartContracts/01c28acab1b91219c811aeb524dcc529 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.2;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
}
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
*
* Furthermore, `isContract` will also return true if the target contract within
* the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
* which only has an effect at the end of a transaction.
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_setOwner(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() external virtual onlyOwner {
_setOwner(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}
/**
* @title Crowdsale
* @dev Base contract for managing a token crowdsale.
* Allows investors to purchase tokens with ether until the contract is paused.
* This contract can be extended to provide additional functionality.
* The external interface represents the basic interface for purchasing tokens.
* The internal interface is for extensible and modifiable surface of crowdsales.
* Do not modify the external interface. Override methods for additional functionality.
*/
contract Crowdsale is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20Metadata;
// The token being sold
IERC20Metadata public token;
// Address where funds are collected
address payable public wallet;
// The rate determines how many token units a buyer receives per wei.
// It represents the conversion between wei and the smallest, indivisible token unit.
// The 'rate' is expressed as a wei-like precision, where a rate of 10^18 is equal to one unit.
// This approach allows for token decimal conversions and rates less than 1.
uint256 public rate;
// Amount of wei raised
uint256 public weiRaised;
/**
* @dev Event emitted when tokens are purchased.
* @param purchaser Address of the user who purchased tokens
* @param beneficiary Address where purchased tokens were sent
* @param value Amount of ether paid for purchase
* @param amount Amount of tokens purchased
*/
event TokensPurchased(address indexed purchaser, address indexed beneficiary, uint256 value, uint256 amount);
/**
* @dev Event emitted when the rate is changed.
* @param rate the new rate
*/
event RateChanged(uint256 rate);
/**
* @dev The rate is the conversion between wei and the smallest and indivisible
* token unit. So, if you are using a rate of 1 with a ERC20Detailed token
* with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
* @param rate_ Number of token units a buyer gets per wei
* @param wallet_ Address where collected funds will be forwarded to
* @param token_ Address of the token being sold
* @param owner_ Address of the contract owner
*/
constructor (uint256 rate_, address payable wallet_, IERC20Metadata token_, address owner_) Ownable() {
setRate(rate_);
setWallet(wallet_);
setToken(token_);
transferOwnership(owner_);
}
/**
* @dev Owner can change the rate.
* @param rate_ Number of token units a buyer gets per wei
*/
function setRate(uint256 rate_) public virtual onlyOwner() {
require(rate_ > 0, "Crowdsale: rate is 0");
require(weiRaised == 0, "Crowdsale has started");
emit RateChanged(rate_);
rate = rate_;
}
/**
* @dev Owner can change the wallet.
* @param wallet_ Address where collected funds will be forwarded to
*/
function setWallet(address payable wallet_) public onlyOwner() {
require(wallet_ != address(0), "Crowdsale: wallet is the zero address");
wallet = wallet_;
}
/**
* @dev Owner can change the token if the crowdsale hasn't started yet.
* @param token_ Address of the token being sold
*/
function setToken(IERC20Metadata token_) public onlyOwner() {
require(weiRaised == 0, "Crowdsale has started");
require(address(token_) != address(0), "Crowdsale: token is the zero address");
token = token_;
}
/**
* @dev Fallback function ***DO NOT OVERRIDE***
* Note that other contracts will transfer funds with a base gas stipend
* of 2300, which is not enough to call buyTokens. Consider calling
* buyTokens directly when purchasing tokens from a contract.
* This function is automatically called when ether is sent to the contract address.
* Users should send Ethers from the beneficiary address in the transaction.
*/
receive() external payable {
buyTokens(_msgSender());
}
/**
* @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
* tokens.
* @param beneficiary Address receiving the tokens
* @param tokenAmount Number of tokens to be purchased
*/
function _processPurchase(address beneficiary, uint256 tokenAmount) virtual internal {
_deliverTokens(beneficiary, tokenAmount);
}
/**
* @dev Low-level token purchase ***DO NOT OVERRIDE***
* Allows users to purchase tokens with ether.
* This function has a non-reentrancy guard, so it shouldn't be called by
* another `nonReentrant` function.
* @param beneficiary Address where purchased tokens will be sent
*/
function buyTokens(address beneficiary) public virtual nonReentrant payable {
uint256 weiAmount = msg.value;
_preValidatePurchase(beneficiary, weiAmount);
require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
require(weiAmount != 0, "Crowdsale: weiAmount is 0");
// Calculate token amount to be created
uint256 tokens = getTokenAmount(weiAmount);
require(tokens != 0, "Crowdsale: cannot buy 0 tokens");
// Update state
weiRaised += weiAmount;
_processPurchase(beneficiary, tokens);
emit TokensPurchased(_msgSender(), beneficiary, weiAmount, tokens);
wallet.transfer(msg.value);
}
/**
* @dev Validation of an incoming purchase. Use require statements to revert state when conditions are not met.
* Use `super` in contracts that inherit from Crowdsale to extend their validations.
* Example from CappedCrowdsale.sol's _preValidatePurchase method:
* super._preValidatePurchase(beneficiary, weiAmount);
* require(weiRaised().add(weiAmount) <= cap);
* @param beneficiary Address performing the token purchase
* @param weiAmount Value in wei involved in the purchase
*/
function _preValidatePurchase(address beneficiary, uint256 weiAmount) virtual internal view {
require(beneficiary != address(0), "Crowdsale: beneficiary is the zero address");
require(weiAmount != 0, "Crowdsale: weiAmount is 0");
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
}
/**
* @dev Calculate the number of tokens that can be purchased with a specified amount of wei.
*
* This function provides a conversion mechanism for converting Ethereum wei (the smallest unit of Ether) into tokens
* based on the current rate and the number of decimal places in the token's representation.
*
* @param weiAmount The amount of wei to be converted into tokens.
* @return The number of tokens that can be purchased with the specified `weiAmount`.
*/
function getTokenAmount(uint256 weiAmount) public view returns (uint256) {
// Calculate the number of tokens using the specified wei amount, rate, and token decimals.
// The formula is: (weiAmount * rate * 10^tokenDecimals) / (10^18 / 10^rateDecimals)
// where 10^18 is used to account for Ether's 18 decimal places and 10^rateDecimals is used to adjust for rate precision.
return (weiAmount * rate * 10**token.decimals()) / 10**36;
}
/**
* @dev Source of tokens. Override this method to modify the way in which the crowdsale ultimately gets and sends
* its tokens.
* @param beneficiary Address performing the token purchase
* @param tokenAmount Number of tokens to be emitted
*/
function _deliverTokens(address beneficiary, uint256 tokenAmount) virtual internal {}
}
/**
* @title BubblegumCrowdsale
* @dev Extension of Crowdsale where tokens are held by a wallet, which approves an allowance to the crowdsale.
*/
contract BubblegumCrowdsale is Crowdsale {
using SafeERC20 for IERC20Metadata;
address private _tokenWallet;
/**
* @dev The rate is the conversion between wei and the smallest and indivisible
* token unit. So, if you are using a rate of 1 with a ERC20Detailed token
* with 3 decimals called TOK, 1 wei will give you 1 unit, or 0.001 TOK.
* @param wallet_ Address where collected funds will be forwarded to
* @param token_ Address of the token being sold
* @param rate_ Number of token units a buyer gets per wei
* @param owner_ Address of the contract owner
* @param distributionWallet_ Address holding the tokens, which has approved allowance to the crowdsale.
*/
constructor (uint256 rate_, address payable wallet_, IERC20Metadata token_, address distributionWallet_, address owner_)
Crowdsale(rate_, wallet_, token_, owner_) {
require(distributionWallet_ != address(0), "AllowanceCrowdsale: token wallet is the zero address");
_tokenWallet = distributionWallet_;
}
/**
* @dev Owner can change the token wallet.
* @param distributionWallet_ Address that holds the token to be distributed
*/
function setTokenWallet(address distributionWallet_) external onlyOwner() {
require(distributionWallet_ != address(0), "Crowdsale: token wallet is the zero address");
_tokenWallet = distributionWallet_;
}
/**
* @return the address of the wallet that will hold the tokens.
*/
function tokenWallet() external view returns (address) {
return _tokenWallet;
}
/**
* @dev Checks the amount of tokens left in the allowance.
* @return Amount of tokens left in the allowance
*/
function remainingTokens() external view returns (uint256) {
return Math.min(token.balanceOf(_tokenWallet), token.allowance(_tokenWallet, address(this)));
}
/**
* @dev Overrides parent behavior by transferring tokens from wallet.
* @param beneficiary Token purchaser
* @param tokenAmount Amount of tokens purchased
*/
function _deliverTokens(address beneficiary, uint256 tokenAmount) internal override {
token.safeTransferFrom(_tokenWallet, beneficiary, tokenAmount);
}
}
/**
* @title TimedCrowdsale
* @dev Crowdsale accepting contributions only within a time frame.
*/
contract TimedCrowdsale is BubblegumCrowdsale {
// Optional opening and closing times
// If both are zero we assume that the ICO is not timed
uint256 private _openingTime;
uint256 private _closingTime;
/**
* Event for crowdsale extending
* @param newClosingTime new closing time
* @param prevClosingTime old closing time
*/
event TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime);
/**
* @dev Reverts if not in crowdsale time range.
*/
modifier onlyWhileOpen {
require(isOpen(), "TimedCrowdsale: not open");
_;
}
constructor (uint256 rate_, address payable wallet_, IERC20Metadata token_, address distributionWallet_, address owner_,
uint256 openingTime_, uint256 closingTime_)
BubblegumCrowdsale(rate_, wallet_, token_, distributionWallet_, owner_) {
require(closingTime_ > openingTime_ || (closingTime_ == 0 && openingTime_ == 0), "TimedCrowdsale: opening time is not before closing time");
_openingTime = openingTime_;
_closingTime = closingTime_;
}
/**
* @dev setTimes, set crowdsale opening and closing times.
* @param openingTime_ Crowdsale opening time
* @param closingTime_ Crowdsale closing time
*/
function setTimes(uint256 openingTime_, uint256 closingTime_) public virtual onlyOwner() {
require(closingTime_ > openingTime_, "TimedCrowdsale: opening time is not before closing time");
_openingTime = openingTime_;
_closingTime = closingTime_;
}
/**
* @return the crowdsale opening time.
*/
function openingTime() public view returns (uint256) {
return _openingTime;
}
/**
* @return the crowdsale closing time.
*/
function closingTime() public view returns (uint256) {
return _closingTime;
}
/**
* @return true if the crowdsale is open, false otherwise.
*/
function isOpen() public view returns (bool) {
// solhint-disable-next-line not-rely-on-time
return (_openingTime == 0 && _closingTime == 0) ||
(block.timestamp >= _openingTime && block.timestamp <= _closingTime);
}
/**
* @dev Checks whether the period in which the crowdsale is open has already elapsed.
* @return Whether crowdsale period has elapsed
*/
function hasClosed() public view returns (bool) {
// solhint-disable-next-line not-rely-on-time
return block.timestamp > _closingTime;
}
/**
* @dev Extend parent behavior requiring to be within contributing period.
* @param beneficiary Token purchaser
* @param weiAmount Amount of wei contributed
*/
function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal override virtual onlyWhileOpen view {
super._preValidatePurchase(beneficiary, weiAmount);
}
}
/**
* @title Roles
* @dev Library for managing addresses assigned to a Role.
*/
library Roles {
struct Role {
mapping (address => bool) bearer;
}
/**
* @dev Give an account access to this role.
*/
function add(Role storage role, address account) internal {
require(!has(role, account), "Roles: account already has role");
role.bearer[account] = true;
}
/**
* @dev Remove an account's access to this role.
*/
function remove(Role storage role, address account) internal {
require(has(role, account), "Roles: account does not have role");
role.bearer[account] = false;
}
/**
* @dev Check if an account has this role.
* @return bool
*/
function has(Role storage role, address account) internal view returns (bool) {
require(account != address(0), "Roles: account is the zero address");
return role.bearer[account];
}
}
/**
* @title WhitelistAdminRole
* @dev WhitelistAdmins are responsible for assigning and removing Whitelisted accounts.
*/
contract WhitelistAdminRole is Context {
using Roles for Roles.Role;
event WhitelistAdminAdded(address indexed account);
event WhitelistAdminRemoved(address indexed account);
Roles.Role private _whitelistAdmins;
constructor() {
_addWhitelistAdmin(_msgSender());
}
modifier onlyWhitelistAdmin() {
require(isWhitelistAdmin(_msgSender()), "WhitelistAdminRole: caller does not have the WhitelistAdmin role");
_;
}
function isWhitelistAdmin(address account) public view returns (bool) {
return _whitelistAdmins.has(account);
}
function addWhitelistAdmin(address account) public onlyWhitelistAdmin {
_addWhitelistAdmin(account);
}
function renounceWhitelistAdmin() public {
_removeWhitelistAdmin(_msgSender());
}
function _addWhitelistAdmin(address account) internal {
_whitelistAdmins.add(account);
emit WhitelistAdminAdded(account);
}
function _removeWhitelistAdmin(address account) internal {
_whitelistAdmins.remove(account);
emit WhitelistAdminRemoved(account);
}
}
/**
* @title WhitelistedRole
* @dev Whitelisted accounts are approved by a WhitelistAdmin to perform certain actions, such as participating in a crowdsale.
* Only WhitelistAdmins can add or remove Whitelisted accounts, and Whitelisted accounts cannot modify their own status.
*/
contract WhitelistedRole is Context, WhitelistAdminRole {
using Roles for Roles.Role;
// Events emitted when an account is added or removed from the whitelist
event WhitelistedAdded(address indexed account);
event WhitelistedRemoved(address indexed account);
// The set of whitelisted accounts
Roles.Role private _whitelisteds;
/**
* @dev Modifier to make a function callable only by whitelisted accounts.
* Reverts if the caller is not whitelisted.
*/
modifier onlyWhitelisted() {
require(isWhitelisted(_msgSender()), "WhitelistedRole: caller does not have the Whitelisted role");
_;
}
/**
* @dev Checks if an account is whitelisted.
* @param account The account to check
* @return True if the account is whitelisted, false otherwise
*/
function isWhitelisted(address account) public view returns (bool) {
return _whitelisteds.has(account);
}
/**
* @dev Adds an account to the whitelist. Only callable by a WhitelistAdmin.
* @param account The account to add
*/
function addWhitelisted(address account) public onlyWhitelistAdmin {
_addWhitelisted(account);
}
/**
* @dev Removes an account from the whitelist. Only callable by a WhitelistAdmin.
* @param account The account to remove
*/
function removeWhitelisted(address account) public onlyWhitelistAdmin {
_removeWhitelisted(account);
}
/**
* @dev Batch adds multiple accounts to the whitelist. Only callable by a WhitelistAdmin.
* @param accounts The array of accounts to add
*/
function addWhitelistedBatch(address[] memory accounts) public onlyWhitelistAdmin {
for (uint256 index = 0; index < accounts.length; index++) {
_addWhitelisted(accounts[index]);
}
}
/**
* @dev Batch removes multiple accounts from the whitelist. Only callable by a WhitelistAdmin.
* @param accounts The array of accounts to remove
*/
function removeWhitelistedBatch(address[] memory accounts) public onlyWhitelistAdmin {
for (uint256 index = 0; index < accounts.length; index++) {
_removeWhitelisted(accounts[index]);
}
}
/**
* @dev Allows a whitelisted account to renounce its role. This can only be called by the account itself.
*/
function renounceWhitelisted() public {
_removeWhitelisted(_msgSender());
}
/**
* @dev Internal function to add an account to the whitelist.
* Emits a WhitelistedAdded event.
* @param account The account to add
*/
function _addWhitelisted(address account) internal {
_whitelisteds.add(account);
emit WhitelistedAdded(account);
}
/**
* @dev Internal function to remove an account from the whitelist.
* Emits a WhitelistedRemoved event.
* @param account The account to remove
*/
function _removeWhitelisted(address account) internal {
_whitelisteds.remove(account);
emit WhitelistedRemoved(account);
}
}
/**
* @title FinalizableCrowdsale
* @dev Extension of TimedCrowdsale with a one-off finalization action, where one
* can do extra work after finishing.
*/
contract FinalizableCrowdsale is TimedCrowdsale {
bool private _finalized;
event CrowdsaleFinalized();
constructor (
uint256 rate_,
address payable wallet_,
IERC20Metadata token_,
address distributionWallet_,
address owner_,
uint256 openingTime_,
uint256 closingTime_
) TimedCrowdsale(rate_, wallet_, token_, distributionWallet_, owner_, openingTime_, closingTime_) {
_finalized = false;
}
/**
* @return true if the crowdsale is finalized, false otherwise.
*/
function finalized() public view returns (bool) {
return _finalized;
}
/**
* @dev Must be called after crowdsale ends, to do some extra finalization
* work. Calls the contract's finalization function.
*/
function finalize() public onlyOwner() {
require(!_finalized, "FinalizableCrowdsale: already finalized");
require(hasClosed(), "FinalizableCrowdsale: not closed");
_finalized = true;
_finalization();
emit CrowdsaleFinalized();
}
/**
* @dev Can be overridden to add finalization logic. The overriding function
* should call super._finalization() to ensure the chain of finalization is
* executed entirely.
*/
function _finalization() internal {
// solhint-disable-previous-line no-empty-blocks
}
/**
* @dev Extend parent behavior requiring to be within contributing period.
* @param openingTime_ Crowdsale opening time
* @param closingTime_ Crowdsale closing time
*/
function setTimes(uint256 openingTime_, uint256 closingTime_) public override onlyOwner() {
require(!finalized(), "FinalizableCrowdsale: finalized");
super.setTimes(openingTime_, closingTime_);
}
}
interface IStakeUbe {
function asset() external returns (IERC20);
function startFor(address _account, uint256 _value) external;
}
/**
* @title PostDeliveryCrowdsale
* @dev This contract implements a crowdsale that locks tokens from withdrawal until the crowdsale ends.
* It supports post-delivery withdrawal or batch distribution of tokens.
*/
contract PostDeliveryCrowdsale is FinalizableCrowdsale {
// Mapping to store the token balances of contributors
mapping(address => uint256) internal _balances;
// Array to keep track of contributors for batch token distribution
address[] public contributors;
// Address of the staking contract, if used
address public stakingContract;
// Token vault to hold tokens until they are delivered
TokenVault internal _vault;
/**
* @dev Flag indicating how tokens should be delivered post-sale.
* 0: No post-delivery, tokens are delivered immediately
* 1: Contributors can withdraw tokens after the sale ends
* 2: Tokens are distributed manually by the owner after the sale ends
*/
uint8 public usePostDelivery;
/**
* @dev Constructor to initialize the PostDeliveryCrowdsale contract.
* @param usePostDelivery_ Post delivery setting: 0 for disabled, 1 for contributor withdraw, 2 for manual distribution
* @param rate_ Token rate (tokens per wei)
* @param wallet_ Address that receives the collected funds
* @param token_ Token being sold
* @param distributionWallet_ Address used for token distribution
* @param openingTime_ Start time for the crowdsale
* @param closingTime_ End time for the crowdsale
* @param owner_ Owner of the contract
*/
constructor (
uint8 usePostDelivery_,
uint256 rate_,
address payable wallet_,
IERC20Metadata token_,
address distributionWallet_,
uint256 openingTime_,
uint256 closingTime_,
address owner_
) FinalizableCrowdsale(rate_, wallet_, token_, distributionWallet_, owner_, openingTime_, closingTime_) {
_vault = new TokenVault(); // Initialize the token vault
usePostDelivery = usePostDelivery_; // Set the post-delivery setting
}
/**
* @dev Transfers tokens to the appropriate recipient (contributor or staking contract).
* @param contributor Address of the contributor receiving the tokens
* @param amount Amount of tokens to transfer
*/
function transferTokens(address contributor, uint256 amount) internal {
_balances[contributor] = 0; // Reset contributor's balance to 0
if (stakingContract == address(0)) {
_vault.transfer(token, contributor, amount); // Transfer directly to the contributor
} else {
_vault.transfer(token, address(this), amount); // Transfer tokens to this contract
token.approve(stakingContract, amount); // Approve staking contract to spend the tokens
IStakeUbe(stakingContract).startFor(contributor, amount); // Start staking for the contributor
}
}
/**
* @dev Allows contributors to withdraw their tokens after the crowdsale ends.
* Only available if post-delivery is set to 1 (contributor withdrawal).
* @param beneficiary Address whose tokens will be withdrawn
*/
function withdrawTokens(address beneficiary) public {
require(usePostDelivery == 1, "PostDeliveryCrowdsale: not activated or no withdrawal available");
require(finalized(), "PostDeliveryCrowdsale: crowdsale not finalized");
uint256 amount = _balances[beneficiary];
require(amount > 0, "PostDeliveryCrowdsale: no tokens due for this beneficiary");
transferTokens(beneficiary, amount); // Transfer tokens to the beneficiary
}
/**
* @dev Sets the staking contract for token staking.
* Can only be called by the owner.
* @param stakes_ Address of the staking contract
*/
function setStakingContract(address stakes_) external onlyOwner {
require(IStakeUbe(stakes_).asset() == token, "PostDeliveryCrowdsale: token addresses don't match");
stakingContract = stakes_; // Set the staking contract address
}
/**
* @dev Distributes tokens to multiple contributors in batches.
* Reserved for the owner. Uses LIFO for gas efficiency.
* @param n Number of contributors to distribute to in this batch
*/
function distributeTokens(uint256 n) external onlyOwner {
require(n <= contributors.length, "PostDeliveryCrowdsale: index out of bounds or no distribution available");
require(finalized(), "PostDeliveryCrowdsale: crowdsale not finalized");
for (uint i = 0; i < n; i++) {
address contributor = contributors[contributors.length - 1];
uint256 amount = _balances[contributor];
if (amount > 0) {
transferTokens(contributor, amount); // Transfer tokens to the contributor
}
contributors.pop(); // Remove the contributor from the list
}
}
/**
* @dev Returns the token balance of an account.
* @param account Address to query the balance of
* @return Balance of the account
*/
function balanceOf(address account) public view returns (uint256) {
return _balances[account];
}
/**
* @dev Processes the purchase by storing the balance or transferring tokens directly.
* Overrides the parent function to handle post-delivery logic.
* @param beneficiary Address of the token purchaser
* @param tokenAmount Amount of tokens purchased
*/
function _processPurchase(address beneficiary, uint256 tokenAmount) override virtual internal {
if (usePostDelivery != 0) {
if (usePostDelivery == 2 && _balances[beneficiary] == 0) {
contributors.push(beneficiary); // Add to contributors list if using batch distribution
}
_balances[beneficiary] += tokenAmount; // Store the token balance
_deliverTokens(address(_vault), tokenAmount); // Deliver tokens to the vault
} else {
super._deliverTokens(beneficiary, tokenAmount); // Deliver tokens directly if no post-delivery
}
}
/**
* @dev Returns the number of contributors.
* @return Number of contributors
*/
function contributorsLength() external view returns (uint256) {
return contributors.length;
}
}
/**
* @dev Secondary contract that can only be used by the account that created it.
* Provides functionality for token transfers in the PostDeliveryCrowdsale.
*/
contract Secondary is Context {
address private _primary;
/**
* @dev Emitted when the primary account is transferred to a new address.
* @param recipient New primary account address
*/
event PrimaryTransferred(address recipient);
/**
* @dev Constructor that sets the primary account to the contract creator.
*/
constructor() {
address msgSender = _msgSender();
_primary = msgSender;
emit PrimaryTransferred(msgSender); // Emit event for primary account setup
}
/**
* @dev Modifier that ensures the function is called only by the primary account.
*/
modifier onlyPrimary() {
require(_msgSender() == _primary, "Secondary: caller is not the primary account");
_;
}
/**
* @return Address of the primary account
*/
function primary() public view returns (address) {
return _primary;
}
/**
* @dev Transfers the primary account role to a new address.
* @param recipient Address of the new primary account
*/
function transferPrimary(address recipient) public onlyPrimary {
require(recipient != address(0), "Secondary: new primary is the zero address");
_primary = recipient; // Transfer the primary account role
emit PrimaryTransferred(recipient); // Emit event for primary transfer
}
}
/**
* @title TokenVault
* @dev A helper contract for PostDeliveryCrowdsale, used to hold and transfer tokens.
* This vault can only be accessed by the primary account (PostDeliveryCrowdsale contract).
*/
contract TokenVault is Secondary {
/**
* @dev Transfers tokens from the vault to a specified address.
* Only callable by the primary account.
* @param token Token to transfer
* @param to Recipient of the tokens
* @param amount Amount of tokens to transfer
*/
function transfer(IERC20 token, address to, uint256 amount) public onlyPrimary {
token.transfer(to, amount); // Transfer tokens from the vault
}
}
/**
*
* @title CaramelCrowdsale
* @dev This contract inherits functionality from WhitelistedRole and PostDeliveryCrowdsale to manage a crowdsale that includes
* several key features:
*
* Features:
* - WhitelistedRole: Allows only approved accounts (whitelisted) to participate in the crowdsale.
* - PostDeliveryCrowdsale: Ensures token delivery happens after the crowdsale has finished.
* - Configurable token rates and contribution limits for multiple tokens, where different tokens can be used as a form of payment.
* - Ether contributions with minimum and maximum limits.
* - A dynamic whitelist that can be enabled or disabled by the owner.
* - The owner has full control to set or change the rate, ether and token-specific limits, and contribution settings during the crowdsale.
*
*/
contract CaramelCrowdsale is WhitelistedRole, PostDeliveryCrowdsale {
using SafeERC20 for IERC20Metadata;
// Event emitted when the whitelist setting is updated
event WhitelistSetting(bool useWhitelist);
/**
* @dev Event emitted when the token rate is changed.
* @param rate the new token rate
*/
event TokenRateChanged(uint256 rate);
/**
* @dev Event emitted when the token minimum contribution is changed.
* @param min the new token minimum contribution
*/
event TokenMinChanged(uint256 min);
/**
* @dev Event emitted when the token maximum contribution is changed.
* @param max the new token maximum contribution
*/
event TokenMaxChanged(uint256 max);
/**
* @dev Event emitted when the ether minimum contribution is changed.
* @param min the new ether minimum contribution
*/
event EtherMinChanged(uint256 min);
/**
* @dev Event emitted when the ether maximum contribution is changed.
* @param max the new ether maximum contribution
*/
event EtherMaxChanged(uint256 max);
/**
* @dev Event emitted when tokens are purchased with tokens
* @param purchaser Address of the user who purchased tokens
* @param paymentToken Address of the payment token
* @param beneficiary Address where purchased tokens were sent
* @param value Amount of ether paid for purchase
* @param amount Amount of tokens purchased
*/
event TokensPurchasedWithTokens(address indexed purchaser, address indexed paymentToken, address indexed beneficiary, uint256 value, uint256 amount);
/**
* @dev Flag indicating if the crowdsale should enforce whitelist checks.
*/
bool public useWhitelist;
/**
* @dev Minimum Ether contribution allowed for the crowdsale.
*/
uint256 public minEtherContribution;
/**
* @dev Maximum Ether contribution allowed for the crowdsale.
*/
uint256 public maxEtherContribution;
/**
* @dev Rates per token's smallest unit (similar to wei for ETH) for each token accepted as a form of payment.
* This rate does not account for token decimals, which should be handled by the interface.
* If the rate is zero, it means that the token is not available for payments.
* If maxContribution is zero it means it is unlimited.
*/
mapping (address => TokenData) public tokenData;
/**
* @dev Struct to hold data related to each token, including the rate and contribution limits.
*/
struct TokenData {
uint256 rate; // Rate per token's smallest unit. It has the same trailing zeros as the buying token
uint256 minContribution; // Minimum token contribution allowed
uint256 maxContribution; // Maximum token contribution allowed
}
/**
* @dev Contribution ledger for each ether/token and contributor
* For ether the first address is 0x0
*/
mapping (address => mapping (address => uint256)) public contributionsLog;
/**
* @dev List of tokens that are available for payments.
*/
address[] public paymentTokens;
/**
* @dev The amount of tokens raised in the crowdsale
*/
mapping(address => uint256) public tokensRaised;
/**
* @dev Constructor that initializes the CaramelCrowdsale contract.
* It sets whether to use the whitelist, and initializes the other crowdsale parameters.
* Adds the owner as a WhitelistAdmin and a whitelisted account.
* @param usePostDelivery_ Determines if post-delivery should be used and how
* @param useWhitelist_ Determines if the whitelist should be enforced
* @param rate_ The rate of tokens per wei
* @param wallet_ The wallet that will receive the funds
* @param token_ The token being sold
* @param distributionWallet_ The wallet for distributing tokens
* @param openingTime_ The crowdsale start time
* @param closingTime_ The crowdsale end time
* @param owner_ The owner of the crowdsale contract
*/
constructor (
uint8 usePostDelivery_,
bool useWhitelist_,
uint256 rate_,
address payable wallet_,
IERC20Metadata token_,
address distributionWallet_,
uint256 openingTime_,
uint256 closingTime_,
address owner_
) PostDeliveryCrowdsale(usePostDelivery_, rate_, wallet_, token_, distributionWallet_, openingTime_, closingTime_, owner_) {
useWhitelist = useWhitelist_; // Set the initial whitelist flag
emit WhitelistSetting(useWhitelist_); // Emit event for whitelist setting
_addWhitelistAdmin(owner_); // Add owner as whitelist admin
_addWhitelisted(owner_); // Add owner to the whitelist
}
/**
* @dev Overrides the parent behavior to require the beneficiary to be whitelisted if the whitelist is enforced.
* Also checks if the crowdsale is finalized before allowing purchases.
* @param _beneficiary The address receiving the tokens
* @param _weiAmount The amount of wei contributed
*/
function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal override view {
// Whitelist validations
if (useWhitelist) {
require(isWhitelisted(_beneficiary), "WhitelistCrowdsale: beneficiary doesn't have the Whitelisted role");
}
require(!finalized(), "CaramelCrowdsale: crowdsale is finalized"); // Ensure crowdsale is not finalized
// Contribution limits validations
require(rate > 0, "CaramelCrowdsale: ether contribution not enabled"); // Ensure rate is set
if (maxEtherContribution > 0) {
// Check the contributions in ether for the user
uint256 totalContributions = contributionsLog[address(0)][msg.sender] + _weiAmount;
require(totalContributions >= minEtherContribution && totalContributions <= maxEtherContribution, "CaramelCrowdsale: contribution is out of bounds"); // Check contribution limits
// Check the contributions in ether for the beneficiary
if (_beneficiary != msg.sender) {
totalContributions = contributionsLog[address(0)][_beneficiary] + _weiAmount;
require(totalContributions >= minEtherContribution && totalContributions <= maxEtherContribution, "CaramelCrowdsale: contributions to the beneficiary is out of bounds"); // Check contribution limits
}
}
super._preValidatePurchase(_beneficiary, _weiAmount); // Call parent validation
}
/**
* @dev Overrides parent by storing total contributions in ether
* @param beneficiary Token purchaser
* @param tokenAmount Amount of tokens purchased
*/
function _processPurchase(address beneficiary, uint256 tokenAmount) override virtual internal {
contributionsLog[address(0)][msg.sender] += msg.value;
super._processPurchase(beneficiary, tokenAmount);
}
/**
* @dev Allows the owner to enable or disable the whitelist enforcement for the crowdsale.
* @param useWhitelist_ Boolean flag to enable or disable the whitelist
*/
function setWhitelist(bool useWhitelist_) external onlyOwner() {
emit WhitelistSetting(useWhitelist_); // Emit event for whitelist update
useWhitelist = useWhitelist_; // Update whitelist setting
}
/**
* @dev Owner can change the rate.
* @param rate_ Number of token units a buyer gets per wei
*/
function setRate(uint256 rate_) public override onlyOwner() {
emit RateChanged(rate_); // Emit event for rate change
rate = rate_; // Set the new rate
}
/**
* @dev Set token-specific data such as rate, minimum, and maximum contribution limits.
* @param token_ The token for which the data is being set
* @param rate_ The rate of tokens per wei for the specified token
* @param min_ Minimum contribution allowed for the token
* @param max_ Maximum contribution allowed for the token
*/
function setTokenData(address token_, uint256 rate_, uint256 min_, uint256 max_) public onlyOwner() {
require(min_ < max_ || (min_ == 0 && max_ == 0), "Invalid minimum and maximum range"); // Validate contribution limits
tokenData[token_].rate = rate_; // Set the rate
tokenData[token_].minContribution = min_; // Set the minimum contribution
tokenData[token_].maxContribution = max_; // Set the maximum contribution
// Emit events for token data changes
emit TokenRateChanged(rate_);
emit TokenMinChanged(min_);
emit TokenMaxChanged(max_);
}
/**
* @dev Set Ether contribution limits.
* @param min_ Minimum Ether contribution allowed
* @param max_ Maximum Ether contribution allowed
*/
function setEtherContributions(uint256 min_, uint256 max_) external onlyOwner() {
require(min_ < max_ || (min_ == 0 && max_ == 0), "CaramelCrowdsale: invalid minimum and maximum range"); // Validate Ether contribution limits
minEtherContribution = min_; // Set the minimum Ether contribution
maxEtherContribution = max_; // Set the maximum Ether contribution
// Emit events for Ether contribution changes
emit EtherMinChanged(min_);
emit EtherMaxChanged(max_);
}
/**
* @dev Returns the length of the payment token array.
* @return length The number of accepted payment tokens.
*/
function paymentTokenLength() external view returns(uint256 length) {
return paymentTokens.length; // Return the number of accepted payment tokens.
}
/**
* @dev Add a payment token if it's not already in the array.
* This function allows the contract owner to add a new payment token to the list of accepted tokens,
* along with its associated rate and contribution limits.
* @param token_ The token to be added to the list of accepted tokens
* @param rate_ The rate of tokens per wei for the specified token
* @param min_ Minimum contribution allowed for the token
* @param max_ Maximum contribution allowed for the token
*/
function addPaymentToken(address token_, uint256 rate_, uint256 min_, uint256 max_) external onlyOwner() {
require(token_ != address(0), "Invalid token address"); // Ensure valid token address
// Check if the token is already in the paymentTokens array
if (!_isTokenInArray(token_)) {
paymentTokens.push(token_); // Add token to the array if it's not already included.
}
// Set the token data
setTokenData(token_, rate_, min_, max_); // Configure the token's rate and contribution limits.
}
/**
* @dev Remove a payment token by swapping it with the last element for efficient removal.
* This function allows the contract owner to remove an accepted payment token from the array.
* @param index The token index to be removed from the list of accepted tokens
*/
function removePaymentToken(uint256 index) external onlyOwner() {
if (index < paymentTokens.length) {
paymentTokens[index] = paymentTokens[paymentTokens.length - 1]; // Swap the token to be removed with the last element.
paymentTokens.pop(); // Remove the last element from the array.
}
}
/**
* @dev Helper function to check if a token exists in the array.
* This function checks if a given token address is already included in the accepted payment tokens array.
* @param token_ The token address to check
* @return bool True if the token exists, false otherwise
*/
function _isTokenInArray(address token_) internal view returns (bool) {
for (uint256 i = 0; i < paymentTokens.length; i++) {
if (paymentTokens[i] == token_) {
return true; // Return true if the token is found in the array.
}
}
return false; // Return false if the token is not found.
}
/**
* @dev Calculate the number of tokens that can be purchased with a specified amount of tokens.
* This function provides a conversion mechanism for converting the ERC-20 smallest unit into tokens bought
* based on the current rate and the number of decimal places in both tokens.
* @param paymentToken The ERC-20 token address used for payment to buy tokens
* @param paymentTokenAmount The amount of tokens paid in paymentToken
* @return uint256 The amount of tokens that can be purchased.
*/
function getTokenAmountWithToken(IERC20Metadata paymentToken, uint256 paymentTokenAmount) public view returns (uint256) {
// Calculate the number of tokens using the formula:
// (paymentTokenAmount * paymentTokenRate) / 10^payingTokenDecimals
return (paymentTokenAmount * tokenData[address(paymentToken)].rate) / 10**paymentToken.decimals();
}
/**
* @dev Low-level token purchase with tokens.
* Allows users to purchase tokens with whitelisted ERC-20 tokens.
* This function has a non-reentrancy guard, so it shouldn't be called by
* another `nonReentrant` function.
* @param paymentToken The ERC-20 payment token address
* @param paymentTokenAmount The amount of the payment token paid (in the smallest unit)
* @param beneficiary Address where purchased tokens will be sent
*/
function buyTokensWithTokens(address paymentToken, uint256 paymentTokenAmount, address beneficiary) public virtual nonReentrant payable {
require(paymentToken != address(0), "CaramelCrowdsale: payment token is the zero address"); // Ensure valid payment token address
require(beneficiary != address(0), "CaramelCrowdsale: beneficiary is the zero address"); // Ensure valid beneficiary address
require(paymentTokenAmount != 0, "CaramelCrowdsale: paymentTokenAmount is 0"); // Ensure a non-zero payment token amount
require(tokenData[paymentToken].rate > 0, "CaramelCrowdsale: payment token is not available"); // Check if payment token is valid
// Whitelist validation if applicable
if (useWhitelist) {
require(isWhitelisted(beneficiary), "WhitelistCrowdsale: beneficiary doesn't have the Whitelisted role"); // Ensure beneficiary is whitelisted.
}
require(!finalized(), "CaramelCrowdsale: crowdsale is finalized"); // Ensure crowdsale is not finalized
// Check contribution limits for the token
if (tokenData[paymentToken].maxContribution > 0) {
// Check the contributions in the token for the user
uint256 totalContributions = contributionsLog[paymentToken][msg.sender] + paymentTokenAmount;
require(totalContributions >= tokenData[paymentToken].minContribution && totalContributions <= tokenData[paymentToken].maxContribution, "CaramelCrowdsale: token contribution is out of bounds"); // Check contribution limits
// Check the contributions for the beneficiary if different from msg.sender
if (beneficiary != msg.sender) {
totalContributions = contributionsLog[paymentToken][beneficiary] + paymentTokenAmount;
require(totalContributions >= tokenData[paymentToken].minContribution && totalContributions <= tokenData[paymentToken].maxContribution, "CaramelCrowdsale: token contribution is out of bounds"); // Check contribution limits
}
}
// Calculate the number of tokens to be issued
uint256 tokens = getTokenAmountWithToken(IERC20Metadata(paymentToken), paymentTokenAmount);
require(tokens != 0, "Crowdsale: cannot buy 0 tokens"); // Ensure that at least one token is purchased
// Update state with raised tokens
tokensRaised[paymentToken] += paymentTokenAmount; // Track total tokens raised in payment tokens
// Process the purchase and assign tokens to the beneficiary
_processPurchase(beneficiary, tokens);
// Emit event for tokens purchased with tokens
emit TokensPurchasedWithTokens(_msgSender(), paymentToken, beneficiary, paymentTokenAmount, tokens);
// Transfer the token payment to the wallet
IERC20Metadata(paymentToken).safeTransferFrom(msg.sender, wallet, paymentTokenAmount); // Transfer the payment to the wallet.
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment