文件 1 的 21:AccessControl.sol
pragma solidity ^0.8.0;
import "./IAccessControl.sol";
import "../utils/Context.sol";
import "../utils/Strings.sol";
import "../utils/introspection/ERC165.sol";
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address => bool) members;
bytes32 adminRole;
}
mapping(bytes32 => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
modifier onlyRole(bytes32 role) {
_checkRole(role, _msgSender());
_;
}
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);
}
function hasRole(bytes32 role, address account) public view override returns (bool) {
return _roles[role].members[account];
}
function _checkRole(bytes32 role, address account) internal view {
if (!hasRole(role, account)) {
revert(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(uint160(account), 20),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
return _roles[role].adminRole;
}
function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
function renounceRole(bytes32 role, address account) public virtual override {
require(account == _msgSender(), "AccessControl: can only renounce roles for self");
_revokeRole(role, account);
}
function _setupRole(bytes32 role, address account) internal virtual {
_grantRole(role, account);
}
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
bytes32 previousAdminRole = getRoleAdmin(role);
_roles[role].adminRole = adminRole;
emit RoleAdminChanged(role, previousAdminRole, adminRole);
}
function _grantRole(bytes32 role, address account) internal virtual {
if (!hasRole(role, account)) {
_roles[role].members[account] = true;
emit RoleGranted(role, account, _msgSender());
}
}
function _revokeRole(bytes32 role, address account) internal virtual {
if (hasRole(role, account)) {
_roles[role].members[account] = false;
emit RoleRevoked(role, account, _msgSender());
}
}
}
文件 2 的 21:Configurable.sol
pragma solidity ^0.8.0;
abstract contract Configurable {
enum State {
UNCONFIGURED,
CONFIGURED
}
State public state = State.UNCONFIGURED;
modifier onlyInState(State _state) {
require(state == _state, "Invalid state");
_;
}
}
文件 3 的 21:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 4 的 21:ERC165.sol
pragma solidity ^0.8.0;
import "./IERC165.sol";
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
文件 5 的 21:ERC20.sol
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";
contract ERC20 is Context, IERC20, IERC20Metadata {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function decimals() public view virtual override returns (uint8) {
return 18;
}
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(_msgSender(), recipient, amount);
return true;
}
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
function approve(address spender, uint256 amount) public virtual override returns (bool) {
_approve(_msgSender(), spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
uint256 currentAllowance = _allowances[sender][_msgSender()];
require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
unchecked {
_approve(sender, _msgSender(), currentAllowance - amount);
}
return true;
}
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
_approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
return true;
}
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
uint256 currentAllowance = _allowances[_msgSender()][spender];
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(_msgSender(), spender, currentAllowance - subtractedValue);
}
return true;
}
function _transfer(
address sender,
address recipient,
uint256 amount
) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(sender, recipient, amount);
uint256 senderBalance = _balances[sender];
require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[sender] = senderBalance - amount;
}
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
_afterTokenTransfer(sender, recipient, amount);
}
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
}
文件 6 的 21:IAccessControl.sol
pragma solidity ^0.8.0;
interface IAccessControl {
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);
function hasRole(bytes32 role, address account) external view returns (bool);
function getRoleAdmin(bytes32 role) external view returns (bytes32);
function grantRole(bytes32 role, address account) external;
function revokeRole(bytes32 role, address account) external;
function renounceRole(bytes32 role, address account) external;
}
文件 7 的 21:IBurnable.sol
pragma solidity ^0.8.0;
interface IBurnable {
function burn(uint256) external;
}
文件 8 的 21:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 9 的 21:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 10 的 21:IERC20Metadata.sol
pragma solidity ^0.8.0;
import "../IERC20.sol";
interface IERC20Metadata is IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
}
文件 11 的 21:ISaleSupply.sol
pragma solidity ^0.8.0;
interface ISaleSupply {
function saleSupply() external view returns (uint256);
}
文件 12 的 21:IToken.sol
pragma solidity ^0.8.0;
import {IBurnable} from "./IBurnable.sol";
import {ISaleSupply} from "./ISaleSupply.sol";
import {IVestingSupply} from "./IVestingSupply.sol";
interface IToken is IBurnable, ISaleSupply, IVestingSupply {}
文件 13 的 21:ITokenERC20.sol
pragma solidity ^0.8.0;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IToken} from "./IToken.sol";
interface ITokenERC20 is IERC20, IToken {}
文件 14 的 21:IVestingSupply.sol
pragma solidity ^0.8.0;
interface IVestingSupply {
function vestingSupply() external view returns (uint256);
}
文件 15 的 21:IWhitelist.sol
pragma solidity ^0.8.0;
interface IWhitelist {
function use(uint256) external returns (bool);
}
文件 16 的 21:Math.sol
pragma solidity ^0.8.0;
library Math {
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function average(uint256 a, uint256 b) internal pure returns (uint256) {
return (a & b) + (a ^ b) / 2;
}
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
return a / b + (a % b == 0 ? 0 : 1);
}
}
文件 17 的 21:Sale.sol
pragma solidity ^0.8.0;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Configurable} from "../utils/Configurable.sol";
import {ITokenERC20} from "../interfaces/ITokenERC20.sol";
import {IWhitelist} from "../interfaces/IWhitelist.sol";
contract Sale is AccessControl, Configurable {
struct Stage {
uint256 supply;
uint256 rate;
uint256 minAlloc;
uint256 openingTime;
uint256 closingTime;
}
struct Phase {
Stage stage;
uint256 soldTokens;
uint256 weiRaised;
}
Phase[] public stages;
ITokenERC20 public erc20;
IWhitelist public whitelist;
address payable public immutable wallet;
uint256 public immutable supply;
uint256 public immutable hardCap;
uint256 public weiRaised;
event TokenPurchase(
address indexed purchaser,
address indexed beneficiary,
uint256 value,
uint256 amount
);
event TokenBurn(uint256 amount);
error SaleNotActive(uint256 timestamp);
error SaleNotFinished(uint256 timestamp);
error NoTokensLeft();
error InvalidConfig(uint256 supply, uint256 cap, address wallet, uint256 stagesCount);
error SupplyMismatch(uint256 supply, uint256 totalSupply);
error ValueMismatch(uint256 hardCap, uint256 totalValue);
error InvalidStageConfig(uint256 rate, uint8 i);
error StartDateInThePast(uint256 start, uint256 now_, uint8 i);
error StartDateNotBeforeEndDate(uint256 start, uint256 end, uint8 i);
error SupplySmallerThanRate(uint256 supply, uint256 rate, uint8 i);
error SupplyConfigurationMishmatch(uint256 saleSupply, uint256 supply);
error BalanceNotEqualSupply(uint256 balance, uint256 supply);
error InvalidReceiver(address receiver);
error NotEnoughBigInvestment(uint256 amount, uint256 minimum);
error HardCapExceeded(uint256 amount, uint256 hardCap);
error StageSupplyDrained(uint256 amount, uint256 supply);
error WhitelistNotPassed(address member, uint256 weiAmount);
modifier onlyWhenActive() {
getCurrentStage();
_;
}
modifier onlyWhenFinished() {
uint256 timestamp = block.timestamp;
if (timestamp < closingTime()) {
revert SaleNotFinished(timestamp);
}
_;
}
constructor(bytes memory arguments_) {
address sender = tx.origin;
(uint256 supply_, uint256 hardCap_, address wallet_, Stage[] memory stages_) = abi.decode(
arguments_,
(uint256, uint256, address, Stage[])
);
uint256 stagesCount = stages_.length;
if (
supply_ == 0 ||
hardCap_ == 0 ||
wallet_ == address(0x0) ||
stagesCount == 0 ||
stagesCount > 16
) {
revert InvalidConfig(supply_, hardCap_, wallet_, stages_.length);
}
uint256 totalSupply;
uint256 totalValue;
uint256 lastClosingTime = block.timestamp;
for (uint8 i = 0; i < stages_.length; i++) {
Stage memory stage = stages_[i];
if (stage.rate == 0) {
revert InvalidStageConfig(stage.rate, i);
}
if (stage.openingTime < lastClosingTime) {
revert StartDateInThePast(stage.openingTime, lastClosingTime, i);
}
if (stage.openingTime >= stage.closingTime) {
revert StartDateNotBeforeEndDate(stage.openingTime, stage.closingTime, i);
}
if (stage.supply < stage.rate) {
revert SupplySmallerThanRate(stage.supply, stage.rate, i);
}
totalValue += stage.supply / stage.rate;
lastClosingTime = stage.closingTime;
totalSupply += stage.supply;
stages.push(Phase(stage, 0, 0));
}
if (supply_ != totalSupply) {
revert SupplyMismatch(supply_, totalSupply);
}
if (hardCap_ != totalValue) {
revert ValueMismatch(hardCap_, totalValue);
}
supply = supply_;
hardCap = hardCap_;
wallet = payable(wallet_);
_setupRole(DEFAULT_ADMIN_ROLE, sender);
}
function configure(address erc20_, address whitelist_)
external
onlyInState(State.UNCONFIGURED)
onlyRole(DEFAULT_ADMIN_ROLE)
{
erc20 = ITokenERC20(erc20_);
whitelist = IWhitelist(whitelist_);
uint256 saleSupply = erc20.saleSupply();
if (saleSupply != supply) {
revert SupplyConfigurationMishmatch(saleSupply, supply);
}
uint256 balance = erc20.balanceOf(address(this));
if (saleSupply != balance) {
revert BalanceNotEqualSupply(balance, saleSupply);
}
state = State.CONFIGURED;
}
function buyTokens(address _beneficiary)
external
payable
onlyInState(State.CONFIGURED)
onlyWhenActive
{
uint8 currentStage = getCurrentStage();
Phase memory phase = stages[currentStage];
uint256 weiAmount = msg.value;
if (_beneficiary == address(0)) {
revert InvalidReceiver(_beneficiary);
}
if (weiAmount < phase.stage.minAlloc) {
revert NotEnoughBigInvestment(weiAmount, phase.stage.minAlloc);
}
uint256 raised = weiRaised + weiAmount;
if (raised > hardCap) {
revert HardCapExceeded(raised, hardCap);
}
uint256 tokenAmount = weiAmount * phase.stage.rate;
uint256 sold = phase.soldTokens + tokenAmount;
if (sold > phase.stage.supply) {
revert StageSupplyDrained(sold, phase.stage.supply);
}
if (address(whitelist) != address(0x0)) {
bool success = whitelist.use(weiAmount);
if (!success) {
revert WhitelistNotPassed(msg.sender, weiAmount);
}
}
weiRaised = raised;
stages[currentStage].weiRaised += weiAmount;
stages[currentStage].soldTokens = sold;
wallet.transfer(weiAmount);
erc20.transfer(_beneficiary, tokenAmount);
emit TokenPurchase(msg.sender, _beneficiary, weiAmount, tokenAmount);
}
receive() external payable {
this.buyTokens(msg.sender);
}
function stageCount() external view returns (uint256) {
return stages.length;
}
function rate() external view returns (uint256) {
return stages[getCurrentStage()].stage.rate;
}
function openingTime() external view returns (uint256) {
return stages[0].stage.openingTime;
}
function closingTime() public view returns (uint256) {
return stages[getLastStage()].stage.closingTime;
}
function tokensLeft() public view onlyInState(State.CONFIGURED) returns (uint256) {
return erc20.balanceOf(address(this));
}
function getLastStage() internal view returns (uint8) {
return uint8(stages.length - 1);
}
function getCurrentStage() public view returns (uint8) {
uint256 timestamp = block.timestamp;
for (uint8 i = 0; i < stages.length; i++) {
if (stages[i].stage.openingTime <= timestamp && timestamp <= stages[i].stage.closingTime) {
return i;
}
}
revert SaleNotActive(timestamp);
}
function hasClosed() external view returns (bool) {
return block.timestamp > closingTime();
}
function finalize() external onlyInState(State.CONFIGURED) onlyWhenFinished {
uint256 tokenAmount = tokensLeft();
if (tokenAmount == 0) {
revert NoTokensLeft();
}
erc20.burn(tokenAmount);
emit TokenBurn(tokenAmount);
}
}
文件 18 的 21:Strings.sol
pragma solidity ^0.8.0;
library Strings {
bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";
function toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
function toHexString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0x00";
}
uint256 temp = value;
uint256 length = 0;
while (temp != 0) {
length++;
temp >>= 8;
}
return toHexString(value, length);
}
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _HEX_SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
}
文件 19 的 21:TokenERC20.sol
pragma solidity ^0.8.0;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IToken} from "../interfaces/IToken.sol";
contract TokenERC20 is ERC20, AccessControl, IToken {
bytes32 public constant CAN_MINT_ROLE = keccak256("CAN MINT");
bytes32 public constant CAN_BURN_ROLE = keccak256("CAN BURN");
uint8 private immutable _decimals;
uint256 private immutable _cap;
uint8 public immutable tax;
address public immutable saleAddress;
uint256 private immutable _saleSupply;
address public immutable vestingAddress;
uint256 private immutable _vestingSupply;
mapping(address => bool) public internalContracts;
error InvalidDecimals(uint8 decimals_);
error SupplyGreaterThanCap(
uint256 supply_,
uint256 saleSupply_,
uint256 vestingSupply_,
uint256 cap_
);
error CapExceeded(uint256 amount_, uint256 cap_);
error InvalidTransactionTax(uint256 percentage_);
error InvalidAllowance(uint256 allowance_, uint256 amount_);
error InvalidSaleConfig(address sale_, uint256 saleSupply_);
error InvalidVestingConfig(address vesting_, uint256 vestingSupply_);
constructor(
string memory name_,
string memory symbol_,
bytes memory arguments_
) ERC20(name_, symbol_) {
address sender = tx.origin;
(
uint8 decimals_,
uint256 cap_,
uint256 initialSupply_,
bool canMint_,
bool canBurn_,
uint8 tax_,
address sale_,
uint256 saleSupply_,
address vesting_,
uint256 vestingSupply_
) = abi.decode(
arguments_,
(uint8, uint256, uint256, bool, bool, uint8, address, uint256, address, uint256)
);
if (decimals_ > 18) {
revert InvalidDecimals(decimals_);
}
if (cap_ == 0) {
cap_ = type(uint256).max;
}
if (initialSupply_ + saleSupply_ + vestingSupply_ > cap_) {
revert SupplyGreaterThanCap(initialSupply_, saleSupply_, vestingSupply_, cap_);
}
if (tax_ > 100) {
revert InvalidTransactionTax(tax_);
}
if ((saleSupply_ > 0 && sale_ == address(0x0)) || (saleSupply_ == 0 && sale_ != address(0x0))) {
revert InvalidSaleConfig(sale_, saleSupply_);
}
if (
(vestingSupply_ > 0 && vesting_ == address(0x0)) ||
(vestingSupply_ == 0 && vesting_ != address(0x0))
) {
revert InvalidVestingConfig(vesting_, vestingSupply_);
}
_decimals = decimals_;
_cap = cap_;
tax = tax_;
if (initialSupply_ > 0) {
_mint(sender, initialSupply_);
}
saleAddress = sale_;
_saleSupply = saleSupply_;
if (sale_ != address(0x0)) {
internalContracts[sale_] = true;
_mint(sale_, saleSupply_);
} else {
if (saleSupply_ != 0) revert InvalidSaleConfig(sale_, saleSupply_);
}
vestingAddress = vesting_;
_vestingSupply = vestingSupply_;
if (vesting_ != address(0x0)) {
internalContracts[vesting_] = true;
_mint(vesting_, vestingSupply_);
} else {
if (vestingSupply_ != 0) revert InvalidVestingConfig(vesting_, vestingSupply_);
}
_setupRole(DEFAULT_ADMIN_ROLE, sender);
_setRoleAdmin(CAN_MINT_ROLE, DEFAULT_ADMIN_ROLE);
_setRoleAdmin(CAN_BURN_ROLE, DEFAULT_ADMIN_ROLE);
if (canMint_) {
_setupRole(CAN_MINT_ROLE, sender);
}
if (canBurn_) {
_setupRole(CAN_BURN_ROLE, sender);
}
if (sale_ != address(0x0)) {
_setupRole(CAN_BURN_ROLE, sale_);
}
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
function cap() public view virtual returns (uint256) {
return _cap;
}
function saleSupply() external view override returns (uint256) {
return _saleSupply;
}
function vestingSupply() external view override returns (uint256) {
return _vestingSupply;
}
function mint(address account, uint256 amount) external onlyRole(CAN_MINT_ROLE) {
_mint(account, amount);
}
function burn(uint256 amount) external override onlyRole(CAN_BURN_ROLE) {
_burn(msg.sender, amount);
}
function _mint(address account, uint256 amount) internal virtual override {
uint256 sum = ERC20.totalSupply() + amount;
if (sum > _cap) {
revert CapExceeded(sum, _cap);
}
super._mint(account, amount);
}
function _calculateTax(uint256 amount) internal view returns (uint256, uint256) {
uint256 burned = (amount * tax) / 100;
uint256 untaxed = amount - burned;
return (burned, untaxed);
}
function isNotInternalTransfer() private view returns (bool) {
return !internalContracts[msg.sender];
}
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
if (tax > 0 && isNotInternalTransfer()) {
(uint256 burned, uint256 untaxed) = _calculateTax(amount);
_burn(msg.sender, burned);
return super.transfer(recipient, untaxed);
} else {
return super.transfer(recipient, amount);
}
}
function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
if (tax > 0 && isNotInternalTransfer()) {
(uint256 burned, uint256 untaxed) = _calculateTax(amount);
uint256 currentAllowance = allowance(sender, _msgSender());
if (currentAllowance < amount) {
revert InvalidAllowance(currentAllowance, amount);
}
unchecked {
_approve(sender, _msgSender(), currentAllowance - burned);
}
_burn(sender, burned);
return super.transferFrom(sender, recipient, untaxed);
} else {
return super.transferFrom(sender, recipient, amount);
}
}
}
文件 20 的 21:Vesting.sol
pragma solidity ^0.8.0;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {Configurable} from "../utils/Configurable.sol";
import {ITokenERC20} from "../interfaces/ITokenERC20.sol";
contract Vesting is AccessControl, Configurable {
struct Shareholder {
address account;
uint8 shares;
}
struct Member {
Shareholder shareholder;
uint256 collected;
uint8 lastCheckpoint;
}
ITokenERC20 public erc20;
mapping(address => Member) public members;
uint256 public immutable supply;
uint8 public immutable duration;
uint256 public startTime;
event Collected(address sender, uint256 amount, uint8 lastCheckpoint, uint8 newCheckpoint);
error InvalidConfig(uint256 supply_, uint8 duration_);
error SharesNotInTheRange(address account, uint256 shares);
error SharesNotSumTo100(uint256 total);
error InvalidMember(address member);
error NothingToCollect(address member, uint8 collected, uint8 checkpoint);
error SupplyMismatch(uint256 balance, uint256 declared);
error ConfigurationBalanceMishmatch(uint256 amount, uint256 balance);
modifier onlyMember() {
if (members[msg.sender].shareholder.shares == 0) {
revert InvalidMember(msg.sender);
}
_;
}
constructor(bytes memory arguments_) {
address sender = tx.origin;
(uint256 supply_, uint8 duration_, Shareholder[] memory shareholders_) = abi.decode(
arguments_,
(uint256, uint8, Shareholder[])
);
if (supply_ == 0 || duration_ == 0 || duration_ > 60) {
revert InvalidConfig(supply_, duration_);
}
uint8 totalShares = 0;
for (uint8 i = 0; i < shareholders_.length; i++) {
Member memory member = Member(shareholders_[i], 0, 0);
uint8 shares = member.shareholder.shares;
address account = member.shareholder.account;
if (account == address(0x0)) {
revert InvalidMember(account);
}
if (shares == 0 || shares > 100) {
revert SharesNotInTheRange(account, shares);
}
members[account] = member;
totalShares += shares;
}
if (totalShares != 100) {
revert SharesNotSumTo100(totalShares);
}
supply = supply_;
duration = duration_;
_setupRole(DEFAULT_ADMIN_ROLE, sender);
}
function configure(address erc20_)
external
onlyInState(State.UNCONFIGURED)
onlyRole(DEFAULT_ADMIN_ROLE)
{
startTime = block.timestamp;
erc20 = ITokenERC20(erc20_);
uint256 balance = erc20.balanceOf(address(this));
if (balance != supply) {
revert SupplyMismatch(balance, supply);
}
uint256 vestingSupply = erc20.vestingSupply();
if (vestingSupply != balance) {
revert ConfigurationBalanceMishmatch(vestingSupply, balance);
}
state = State.CONFIGURED;
}
function endTime() public view onlyInState(State.CONFIGURED) returns (uint256) {
return startTime + (30 days * duration);
}
function currentCheckpoint() public view onlyInState(State.CONFIGURED) returns (uint8) {
if (startTime > block.timestamp) return 0;
uint256 checkpoint = (block.timestamp - startTime) / 30 days;
return uint8(Math.min(checkpoint, uint256(duration)));
}
function collect() external onlyInState(State.CONFIGURED) onlyMember {
address sender = msg.sender;
uint8 checkpoint = currentCheckpoint();
uint8 lastCheckpoint = members[sender].lastCheckpoint;
if (checkpoint <= lastCheckpoint) {
revert NothingToCollect(sender, lastCheckpoint, checkpoint);
}
uint256 amount;
if (checkpoint == duration) {
amount = (supply * members[sender].shareholder.shares) / 100 - members[sender].collected;
} else {
uint8 checkpointsToCollect = checkpoint - lastCheckpoint;
uint256 partialSupply = supply / duration;
uint256 singleCheckpointAmount = (partialSupply * members[sender].shareholder.shares) / 100;
amount = checkpointsToCollect * singleCheckpointAmount;
}
members[sender].lastCheckpoint = checkpoint;
members[sender].collected += amount;
erc20.transfer(sender, amount);
emit Collected(sender, amount, lastCheckpoint, checkpoint);
}
}
文件 21 的 21:Whitelist.sol
pragma solidity ^0.8.0;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {Configurable} from "../utils/Configurable.sol";
import {IWhitelist} from "../interfaces/IWhitelist.sol";
contract Whitelist is AccessControl, Configurable, IWhitelist {
bytes32 public constant CAN_MANAGE_ROLE = keccak256("CAN MANAGE");
struct Member {
address account;
uint256 allowance;
}
struct Whitelisted {
uint256 allowance;
uint256 used;
}
mapping(address => Whitelisted) public members;
address public sale;
event AccountNotWhitelisted(address account);
event NotEnoughAllowance(address account, uint256 allowance, uint256 amount);
event WhitelistUpdated(uint256 created, uint256 updated, uint256 deleted);
error InvalidAccount(address account, uint8 i);
error AccountAlreadyWhitelisted(address account);
error AccountDoesNotExist(address account);
error InvalidSender(address account);
error UsedBiggerThanAllowance(address account, uint256 used, uint256 newAllowance);
modifier onlySale() {
address sender = msg.sender;
if (sender != sale) {
revert InvalidSender(sender);
}
_;
}
constructor(bytes memory arguments_) {
address sender = tx.origin;
Member[] memory members_ = abi.decode(arguments_, (Member[]));
for (uint8 i = 0; i < members_.length; i++) {
Member memory member = members_[i];
if (member.account == address(0x0)) {
revert InvalidAccount(member.account, i);
}
if (member.allowance == 0) {
member.allowance = type(uint256).max;
}
members[member.account] = Whitelisted(member.allowance, 0);
}
_setupRole(DEFAULT_ADMIN_ROLE, sender);
_setRoleAdmin(CAN_MANAGE_ROLE, DEFAULT_ADMIN_ROLE);
_setupRole(CAN_MANAGE_ROLE, sender);
}
function configure(address sale_)
external
onlyInState(State.UNCONFIGURED)
onlyRole(DEFAULT_ADMIN_ROLE)
{
sale = sale_;
state = State.CONFIGURED;
}
function update(
Member[] memory toCreate,
Member[] memory toUpdate,
address[] memory toDelete
) external onlyRole(CAN_MANAGE_ROLE) {
for (uint8 i = 0; i < toCreate.length; i++) {
Member memory member = toCreate[i];
if (members[member.account].allowance != 0) {
revert AccountAlreadyWhitelisted(member.account);
}
if (member.allowance == 0) {
member.allowance = type(uint256).max;
}
members[member.account] = Whitelisted(member.allowance, 0);
}
for (uint8 i = 0; i < toUpdate.length; i++) {
Member memory member = toUpdate[i];
if (members[member.account].allowance == 0) {
revert AccountDoesNotExist(member.account);
}
if (member.allowance == 0) {
member.allowance = type(uint256).max;
}
uint256 used = members[member.account].used;
if (used > member.allowance) {
revert UsedBiggerThanAllowance(member.account, used, member.allowance);
}
members[member.account].allowance = member.allowance;
}
for (uint8 i = 0; i < toDelete.length; i++) {
address account = toDelete[i];
if (members[account].allowance == 0) {
revert AccountDoesNotExist(account);
}
members[account] = Whitelisted(0, 0);
}
emit WhitelistUpdated(toCreate.length, toUpdate.length, toDelete.length);
}
function use(uint256 amount)
external
override
onlyInState(State.CONFIGURED)
onlySale
returns (bool)
{
address sender = tx.origin;
Whitelisted memory whitelisted = members[sender];
if (whitelisted.allowance == 0) {
emit AccountNotWhitelisted(sender);
return false;
}
uint256 allowance = whitelisted.allowance;
if (allowance < whitelisted.used + amount) {
emit NotEnoughAllowance(sender, allowance, amount);
return false;
}
members[sender].used += amount;
return true;
}
}
{
"compilationTarget": {
"contracts/tokens/TokenERC20.sol": "TokenERC20"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"string","name":"name_","type":"string"},{"internalType":"string","name":"symbol_","type":"string"},{"internalType":"bytes","name":"arguments_","type":"bytes"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"amount_","type":"uint256"},{"internalType":"uint256","name":"cap_","type":"uint256"}],"name":"CapExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"allowance_","type":"uint256"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"InvalidAllowance","type":"error"},{"inputs":[{"internalType":"uint8","name":"decimals_","type":"uint8"}],"name":"InvalidDecimals","type":"error"},{"inputs":[{"internalType":"address","name":"sale_","type":"address"},{"internalType":"uint256","name":"saleSupply_","type":"uint256"}],"name":"InvalidSaleConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"percentage_","type":"uint256"}],"name":"InvalidTransactionTax","type":"error"},{"inputs":[{"internalType":"address","name":"vesting_","type":"address"},{"internalType":"uint256","name":"vestingSupply_","type":"uint256"}],"name":"InvalidVestingConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"supply_","type":"uint256"},{"internalType":"uint256","name":"saleSupply_","type":"uint256"},{"internalType":"uint256","name":"vestingSupply_","type":"uint256"},{"internalType":"uint256","name":"cap_","type":"uint256"}],"name":"SupplyGreaterThanCap","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"CAN_BURN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CAN_MINT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"cap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"internalContracts","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"saleAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"saleSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tax","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vestingAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vestingSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]