编译器
0.8.22+commit.4fc1097e
文件 1 的 12:AccessControl.sol
pragma solidity ^0.8.20;
import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "./Context.sol";
import {ERC165} from "./ERC165.sol";
abstract contract AccessControl is Context, IAccessControl, ERC165 {
struct RoleData {
mapping(address account => bool) hasRole;
bytes32 adminRole;
}
mapping(bytes32 role => RoleData) private _roles;
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
modifier onlyRole(bytes32 role) {
_checkRole(role);
_;
}
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 virtual returns (bool) {
return _roles[role].hasRole[account];
}
function _checkRole(bytes32 role) internal view virtual {
_checkRole(role, _msgSender());
}
function _checkRole(bytes32 role, address account) internal view virtual {
if (!hasRole(role, account)) {
revert AccessControlUnauthorizedAccount(account, role);
}
}
function getRoleAdmin(bytes32 role) public view virtual returns (bytes32) {
return _roles[role].adminRole;
}
function grantRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_grantRole(role, account);
}
function revokeRole(bytes32 role, address account) public virtual onlyRole(getRoleAdmin(role)) {
_revokeRole(role, account);
}
function renounceRole(bytes32 role, address callerConfirmation) public virtual {
if (callerConfirmation != _msgSender()) {
revert AccessControlBadConfirmation();
}
_revokeRole(role, callerConfirmation);
}
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 returns (bool) {
if (!hasRole(role, account)) {
_roles[role].hasRole[account] = true;
emit RoleGranted(role, account, _msgSender());
return true;
} else {
return false;
}
}
function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
if (hasRole(role, account)) {
_roles[role].hasRole[account] = false;
emit RoleRevoked(role, account, _msgSender());
return true;
} else {
return false;
}
}
}
文件 2 的 12:Address.sol
pragma solidity ^0.8.20;
library Address {
error AddressInsufficientBalance(address account);
error AddressEmptyCode(address target);
error FailedInnerCall();
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
function _revert(bytes memory returndata) private pure {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
文件 3 的 12:Context.sol
pragma solidity ^0.8.20;
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 的 12:ERC165.sol
pragma solidity ^0.8.20;
import {IERC165} from "./IERC165.sol";
abstract contract ERC165 is IERC165 {
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == type(IERC165).interfaceId;
}
}
文件 5 的 12:FundRaisingGuild.sol
pragma solidity ^0.8.22;
import "./IERC20.sol";
import "./SafeERC20.sol";
contract FundRaisingGuild {
using SafeERC20 for IERC20;
address public stakingContract;
constructor(address _stakingContract) {
stakingContract = _stakingContract;
}
function withdrawTo(IERC20 _token, address _recipient, uint256 _amount) external {
require(msg.sender == stakingContract, "Guild.withdrawTo: Only staking contract");
_token.safeTransfer(_recipient, _amount);
}
function tokenBalance(IERC20 _token) external view returns (uint256) {
return _token.balanceOf(address(this));
}
}
文件 6 的 12:IAccessControl.sol
pragma solidity ^0.8.20;
interface IAccessControl {
error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);
error AccessControlBadConfirmation();
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 callerConfirmation) external;
}
文件 7 的 12:IERC165.sol
pragma solidity ^0.8.20;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 8 的 12:IERC20.sol
pragma solidity ^0.8.20;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
文件 9 的 12:IERC20Permit.sol
pragma solidity ^0.8.20;
interface IERC20Permit {
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function nonces(address owner) external view returns (uint256);
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
文件 10 的 12:LaunchPoolAccessControlERC20FundRaisingWithDelayedVesting.sol
pragma solidity ^0.8.22;
import "./IERC20.sol";
import "./SafeERC20.sol";
import "./ReentrancyGuard.sol";
import "./AccessControl.sol";
import {FundRaisingGuild} from "./FundRaisingGuild.sol";
contract LaunchPoolAccessControlERC20FundRaisingWithDelayedVesting is ReentrancyGuard, AccessControl {
bytes32 public constant ADMIN_ROLE = 0xa49807205ce4d355092ef5a8a18f56e8913cf4a201fbe287825b095693c21775;
bytes32 public constant POOL_MANAGEMENT_ROLE = 0x748e6e704f9e8a0607dcd3ee9838387c62f0e3ff37c2fdfde1bff8fd2bfd2729;
bytes32 public constant FUND_MANAGEMENT_ROLE = 0x242fde9ae62216221a403f62519bf9b82fc4b05920f032ad90c1f21986721dc2;
using SafeERC20 for IERC20;
struct UserInfo {
uint256 amount;
uint256 pledgeFundingAmount;
uint256 rewardDebtRewards;
uint256 tokenAllocDebt;
}
struct PoolInfo {
IERC20 rewardToken;
IERC20 fundRaisingToken;
uint256 tokenAllocationStartTimestamp;
uint256 stakingEndTimestamp;
uint256 pledgeFundingEndTimestamp;
uint256 targetRaise;
uint256 maxStakingAmountPerUser;
}
IERC20 public stakingToken;
FundRaisingGuild public rewardGuildBank;
PoolInfo[] public poolInfo;
mapping(uint256 => uint256) public poolIdToAccPercentagePerShare;
mapping(uint256 => uint256) public poolIdToLastPercentageAllocTimestamp;
mapping(uint256 => uint256) public poolIdToRewardPerTimestamp;
mapping(uint256 => uint256) public poolIdToLastRewardTimestamp;
mapping(uint256 => uint256) public poolIdToRewardStartTimestamp;
mapping(uint256 => uint256) public poolIdToRewardCliffEndTimestamp;
mapping(uint256 => uint256) public poolIdToRewardEndTimestamp;
mapping(uint256 => uint256) public poolIdToAccRewardPerShareVesting;
mapping(uint256 => uint256) public poolIdToMaxRewardTokensAvailableForVesting;
mapping(uint256 => uint256) public poolIdToTotalStaked;
mapping(uint256 => uint256) public poolIdToTotalRaised;
mapping(uint256 => uint256) public poolIdToTotalFundedPercentageOfTargetRaise;
mapping(uint256 => bool) public poolIdToFundsClaimed;
mapping(uint256 => mapping(address => UserInfo)) public userInfo;
uint256 public constant TOTAL_TOKEN_ALLOCATION_POINTS = (100 * (10 ** 18));
event ContractDeployed(address indexed guildBank);
event PoolAdded(uint256 indexed pid);
event PoolRewardTokenAdded(uint256 indexed pid, address rewardToken);
event Pledge(address indexed user, uint256 indexed pid, uint256 amount);
event PledgeFunded(address indexed user, uint256 indexed pid, uint256 amount);
event RewardsSetUp(uint256 indexed pid, uint256 amount, uint256 rewardEndTimestamp);
event RewardClaimed(address indexed user, uint256 indexed pid, uint256 amount);
event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
event FundRaisingClaimed(uint256 indexed pid, address indexed recipient, uint256 amount);
constructor(IERC20 _stakingToken, address[] memory admins) {
require(
_checkZeroAddress(address(_stakingToken)) && _checkDeadAddress(address(_stakingToken)),
"constructor: _stakingToken must not be zero address"
);
stakingToken = _stakingToken;
rewardGuildBank = new FundRaisingGuild(address(this));
_setRoleAdmin(POOL_MANAGEMENT_ROLE, ADMIN_ROLE);
_setRoleAdmin(FUND_MANAGEMENT_ROLE, ADMIN_ROLE);
for (uint256 i = 0; i < admins.length; ) {
_grantRole(ADMIN_ROLE, admins[i]);
unchecked {
++i;
}
}
emit ContractDeployed(address(rewardGuildBank));
}
function batchGrantRole(bytes32[] memory roles, address[] memory accounts) public onlyRole(ADMIN_ROLE) {
require(roles.length == accounts.length, "batchGrantRole: roles and accounts must be same length");
for (uint256 i = 0; i < accounts.length; ) {
_grantRole(roles[i], accounts[i]);
unchecked {
++i;
}
}
}
function numberOfPools() external view returns (uint256) {
return poolInfo.length;
}
function add(
IERC20 _rewardToken,
IERC20 _fundRaisingToken,
uint256 _tokenAllocationStartTimestamp,
uint256 _stakingEndTimestamp,
uint256 _pledgeFundingEndTimestamp,
uint256 _targetRaise,
uint256 _maxStakingAmountPerUser,
bool _withUpdate
) public onlyRole(POOL_MANAGEMENT_ROLE) {
address fundRaisingTokenAddress = address(_fundRaisingToken);
require(
_checkZeroAddress(fundRaisingTokenAddress) && _checkDeadAddress(fundRaisingTokenAddress),
"add: _fundRaisingToken is zero address"
);
require(
_tokenAllocationStartTimestamp < _stakingEndTimestamp,
"add: _tokenAllocationStartTimestamp must be before staking end"
);
require(_stakingEndTimestamp < _pledgeFundingEndTimestamp, "add: staking end must be before funding end");
require(_targetRaise > 0, "add: Invalid raise amount");
if (_withUpdate) {
massUpdatePools();
}
poolInfo.push(
PoolInfo({
rewardToken: _rewardToken,
fundRaisingToken: _fundRaisingToken,
tokenAllocationStartTimestamp: _tokenAllocationStartTimestamp,
stakingEndTimestamp: _stakingEndTimestamp,
pledgeFundingEndTimestamp: _pledgeFundingEndTimestamp,
targetRaise: _targetRaise,
maxStakingAmountPerUser: _maxStakingAmountPerUser
})
);
poolIdToLastPercentageAllocTimestamp[poolInfo.length - 1] = _tokenAllocationStartTimestamp;
emit PoolAdded(poolInfo.length - 1);
}
function pledge(uint256 _pid, uint256 _amount) external nonReentrant {
require(_pid < poolInfo.length, "pledge: Invalid PID");
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(_amount > 0, "pledge: No pledge specified");
require(block.timestamp <= pool.stakingEndTimestamp, "pledge: Staking no longer permitted");
require(
user.amount + _amount <= pool.maxStakingAmountPerUser,
"pledge: can not exceed max staking amount per user"
);
updatePool(_pid);
user.amount = user.amount + _amount;
user.tokenAllocDebt = user.tokenAllocDebt + (_amount * poolIdToAccPercentagePerShare[_pid]) / 1e18;
poolIdToTotalStaked[_pid] = poolIdToTotalStaked[_pid] + _amount;
stakingToken.safeTransferFrom(address(msg.sender), address(this), _amount);
emit Pledge(msg.sender, _pid, _amount);
}
function getPledgeFundingAmount(uint256 _pid) public view returns (uint256) {
require(_pid < poolInfo.length, "getPledgeFundingAmount: Invalid PID");
PoolInfo memory pool = poolInfo[_pid];
UserInfo memory user = userInfo[_pid][msg.sender];
(uint256 accPercentPerShare, ) = getAccPercentagePerShareAndLastAllocTimestamp(_pid);
uint256 userPercentageAllocated = (user.amount * accPercentPerShare) / 1e18 - user.tokenAllocDebt;
return (userPercentageAllocated * pool.targetRaise) / TOTAL_TOKEN_ALLOCATION_POINTS;
}
function fundPledge(uint256 _pid) external nonReentrant {
require(_pid < poolInfo.length, "fundPledge: Invalid PID");
updatePool(_pid);
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.pledgeFundingAmount == 0, "fundPledge: Pledge has already been funded");
require(block.timestamp > pool.stakingEndTimestamp, "fundPledge: Staking is still taking place");
require(
block.timestamp <= pool.pledgeFundingEndTimestamp,
"fundPledge: Deadline has passed to fund your pledge"
);
require(user.amount > 0, "fundPledge: Must have staked");
uint256 pledgeFundingAmount = getPledgeFundingAmount(_pid);
require(pledgeFundingAmount > 0, "fundPledge: must have positive pledge amount");
pool.fundRaisingToken.safeTransferFrom(msg.sender, address(this), pledgeFundingAmount);
poolIdToTotalRaised[_pid] = poolIdToTotalRaised[_pid] + pledgeFundingAmount;
(uint256 accPercentPerShare, ) = getAccPercentagePerShareAndLastAllocTimestamp(_pid);
uint256 userPercentageAllocated = (user.amount * accPercentPerShare) / 1e18 - user.tokenAllocDebt;
poolIdToTotalFundedPercentageOfTargetRaise[_pid] =
poolIdToTotalFundedPercentageOfTargetRaise[_pid] +
userPercentageAllocated;
user.pledgeFundingAmount = pledgeFundingAmount;
stakingToken.safeTransfer(address(msg.sender), user.amount);
emit PledgeFunded(msg.sender, _pid, pledgeFundingAmount);
}
function getTotalRaisedVsTarget(uint256 _pid) external view returns (uint256 raised, uint256 target) {
return (poolIdToTotalRaised[_pid], poolInfo[_pid].targetRaise);
}
function setRewardTokenOnPool(uint256 _pid, IERC20 _rewardToken) external onlyRole(POOL_MANAGEMENT_ROLE) {
require(_pid < poolInfo.length, "setRewardTokenOnPool: Invalid PID");
address rewardTokenAddress = address(_rewardToken);
require(
_checkZeroAddress(rewardTokenAddress) && _checkDeadAddress(rewardTokenAddress),
"setRewardTokenOnPool: _rewardToken is zero address"
);
PoolInfo storage pool = poolInfo[_pid];
require(address(pool.rewardToken) == address(0), "setRewardTokenOnPool: rewardToken already set");
pool.rewardToken = _rewardToken;
emit PoolRewardTokenAdded(_pid, rewardTokenAddress);
}
function setupVestingRewards(
uint256 _pid,
uint256 _rewardAmount,
uint256 _rewardStartTimestamp,
uint256 _rewardCliffEndTimestamp,
uint256 _rewardEndTimestamp
) external nonReentrant onlyRole(POOL_MANAGEMENT_ROLE) {
require(_pid < poolInfo.length, "setupVestingRewards: Invalid PID");
require(_rewardStartTimestamp > block.timestamp, "setupVestingRewards: start timestamp in the past");
require(
_rewardCliffEndTimestamp >= _rewardStartTimestamp,
"setupVestingRewards: Cliff must be after or equal to start timestamp"
);
require(
_rewardEndTimestamp > _rewardCliffEndTimestamp,
"setupVestingRewards: end timestamp must be after cliff timestamp"
);
PoolInfo storage pool = poolInfo[_pid];
address rewardTokenAddress = address(pool.rewardToken);
require(
_checkZeroAddress(rewardTokenAddress) && _checkDeadAddress(rewardTokenAddress),
"setupVestingRewards: rewardToken is zero address"
);
require(block.timestamp > pool.pledgeFundingEndTimestamp, "setupVestingRewards: Stakers are still pledging");
uint256 vestingLength = _rewardEndTimestamp - _rewardStartTimestamp;
poolIdToMaxRewardTokensAvailableForVesting[_pid] = _rewardAmount;
poolIdToRewardPerTimestamp[_pid] = _rewardAmount / vestingLength;
poolIdToRewardStartTimestamp[_pid] = _rewardStartTimestamp;
poolIdToLastRewardTimestamp[_pid] = _rewardStartTimestamp;
poolIdToRewardCliffEndTimestamp[_pid] = _rewardCliffEndTimestamp;
poolIdToRewardEndTimestamp[_pid] = _rewardEndTimestamp;
pool.rewardToken.safeTransferFrom(msg.sender, address(rewardGuildBank), _rewardAmount);
emit RewardsSetUp(_pid, _rewardAmount, _rewardEndTimestamp);
}
function pendingRewards(uint256 _pid, address _user) external view returns (uint256) {
require(_pid < poolInfo.length, "pendingRewards: invalid _pid");
UserInfo memory user = userInfo[_pid][_user];
if (user.pledgeFundingAmount == 0) {
return 0;
}
uint256 accRewardPerShare = poolIdToAccRewardPerShareVesting[_pid];
uint256 rewardEndTimestamp = poolIdToRewardEndTimestamp[_pid];
uint256 lastRewardTimestamp = poolIdToLastRewardTimestamp[_pid];
uint256 rewardPerTimestamp = poolIdToRewardPerTimestamp[_pid];
if (block.timestamp > lastRewardTimestamp && rewardEndTimestamp != 0 && poolIdToTotalStaked[_pid] != 0) {
uint256 maxEndTimestamp = block.timestamp <= rewardEndTimestamp ? block.timestamp : rewardEndTimestamp;
uint256 multiplier = getMultiplier(lastRewardTimestamp, maxEndTimestamp);
uint256 reward = multiplier * rewardPerTimestamp;
accRewardPerShare = accRewardPerShare + (reward * 1e18) / poolIdToTotalFundedPercentageOfTargetRaise[_pid];
}
(uint256 accPercentPerShare, ) = getAccPercentagePerShareAndLastAllocTimestamp(_pid);
uint256 userPercentageAllocated = (user.amount * accPercentPerShare) / 1e18 - user.tokenAllocDebt;
return (userPercentageAllocated * accRewardPerShare) / 1e18 - user.rewardDebtRewards;
}
function massUpdatePools() public {
for (uint256 pid = 0; pid < poolInfo.length; pid++) {
updatePool(pid);
}
}
function updatePool(uint256 _pid) public {
require(_pid < poolInfo.length, "updatePool: invalid _pid");
PoolInfo storage _poolInfo = poolInfo[_pid];
if (block.timestamp < _poolInfo.tokenAllocationStartTimestamp) {
return;
}
if (poolIdToTotalStaked[_pid] == 0) {
poolIdToLastPercentageAllocTimestamp[_pid] = block.timestamp;
return;
}
uint256 maxEndTimestampForPercentAlloc = block.timestamp <= _poolInfo.stakingEndTimestamp
? block.timestamp
: _poolInfo.stakingEndTimestamp;
uint256 timestampsSinceLastPercentAlloc = getMultiplier(
poolIdToLastPercentageAllocTimestamp[_pid],
maxEndTimestampForPercentAlloc
);
if (poolIdToRewardEndTimestamp[_pid] == 0 && timestampsSinceLastPercentAlloc > 0) {
(uint256 accPercentPerShare, uint256 lastAllocTimestamp) = getAccPercentagePerShareAndLastAllocTimestamp(
_pid
);
poolIdToAccPercentagePerShare[_pid] = accPercentPerShare;
poolIdToLastPercentageAllocTimestamp[_pid] = lastAllocTimestamp;
}
if (poolIdToRewardEndTimestamp[_pid] == 0) {
return;
}
if (block.timestamp < poolIdToRewardCliffEndTimestamp[_pid]) {
return;
}
uint256 rewardEndTimestamp = poolIdToRewardEndTimestamp[_pid];
uint256 lastRewardTimestamp = poolIdToLastRewardTimestamp[_pid];
uint256 maxEndTimestamp = block.timestamp <= rewardEndTimestamp ? block.timestamp : rewardEndTimestamp;
uint256 multiplier = getMultiplier(lastRewardTimestamp, maxEndTimestamp);
if (multiplier == 0) {
return;
}
uint256 rewardPerTimestamp = poolIdToRewardPerTimestamp[_pid];
uint256 reward = multiplier * rewardPerTimestamp;
poolIdToAccRewardPerShareVesting[_pid] =
poolIdToAccRewardPerShareVesting[_pid] +
(reward * 1e18) /
poolIdToTotalFundedPercentageOfTargetRaise[_pid];
poolIdToLastRewardTimestamp[_pid] = maxEndTimestamp;
}
function getAccPercentagePerShareAndLastAllocTimestamp(
uint256 _pid
) internal view returns (uint256 accPercentPerShare, uint256 lastAllocTimestamp) {
PoolInfo memory _poolInfo = poolInfo[_pid];
uint256 tokenAllocationPeriodInTimestamps = _poolInfo.stakingEndTimestamp -
_poolInfo.tokenAllocationStartTimestamp;
uint256 allocationAvailablePerTimestamp = TOTAL_TOKEN_ALLOCATION_POINTS / tokenAllocationPeriodInTimestamps;
uint256 maxEndTimestampForPercentAlloc = block.timestamp <= _poolInfo.stakingEndTimestamp
? block.timestamp
: _poolInfo.stakingEndTimestamp;
uint256 multiplier = getMultiplier(poolIdToLastPercentageAllocTimestamp[_pid], maxEndTimestampForPercentAlloc);
uint256 totalPercentageUnlocked = multiplier * allocationAvailablePerTimestamp;
return (
poolIdToAccPercentagePerShare[_pid] + (totalPercentageUnlocked * 1e18) / poolIdToTotalStaked[_pid],
maxEndTimestampForPercentAlloc
);
}
function claimReward(uint256 _pid) public nonReentrant {
updatePool(_pid);
require(block.timestamp >= poolIdToRewardCliffEndTimestamp[_pid], "claimReward: Not past cliff");
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.pledgeFundingAmount > 0, "claimReward: Nice try pal");
PoolInfo storage pool = poolInfo[_pid];
address rewardTokenAddress = address(pool.rewardToken);
require(
_checkZeroAddress(address(rewardTokenAddress)) && _checkDeadAddress(address(rewardTokenAddress)),
"claimReward: rewardToken is zero address"
);
uint256 accRewardPerShare = poolIdToAccRewardPerShareVesting[_pid];
(uint256 accPercentPerShare, ) = getAccPercentagePerShareAndLastAllocTimestamp(_pid);
uint256 userPercentageAllocated = (user.amount * accPercentPerShare) / 1e18 - user.tokenAllocDebt;
uint256 pending = (userPercentageAllocated * accRewardPerShare) / 1e18 - user.rewardDebtRewards;
if (pending > 0) {
user.rewardDebtRewards = (userPercentageAllocated * accRewardPerShare) / 1e18;
safeRewardTransfer(pool.rewardToken, msg.sender, pending);
emit RewardClaimed(msg.sender, _pid, pending);
}
}
function withdraw(uint256 _pid) external nonReentrant {
require(_pid < poolInfo.length, "withdraw: invalid _pid");
PoolInfo storage pool = poolInfo[_pid];
UserInfo storage user = userInfo[_pid][msg.sender];
require(user.amount > 0, "withdraw: No stake to withdraw");
require(user.pledgeFundingAmount == 0, "withdraw: Only allow non-funders to withdraw");
require(block.timestamp > pool.pledgeFundingEndTimestamp, "withdraw: Not yet permitted");
uint256 withdrawAmount = user.amount;
delete userInfo[_pid][msg.sender];
stakingToken.safeTransfer(msg.sender, withdrawAmount);
emit Withdraw(msg.sender, _pid, withdrawAmount);
}
function claimFundRaising(uint256 _pid) external nonReentrant onlyRole(FUND_MANAGEMENT_ROLE) {
require(_pid < poolInfo.length, "claimFundRaising: invalid _pid");
PoolInfo storage pool = poolInfo[_pid];
uint256 rewardPerTimestamp = poolIdToRewardPerTimestamp[_pid];
require(rewardPerTimestamp != 0, "claimFundRaising: rewards not yet sent");
require(poolIdToFundsClaimed[_pid] == false, "claimFundRaising: Already claimed funds");
poolIdToFundsClaimed[_pid] = true;
pool.fundRaisingToken.transfer(msg.sender, poolIdToTotalRaised[_pid]);
emit FundRaisingClaimed(_pid, msg.sender, poolIdToTotalRaised[_pid]);
}
function safeRewardTransfer(IERC20 _rewardToken, address _to, uint256 _amount) private {
uint256 bal = rewardGuildBank.tokenBalance(_rewardToken);
if (_amount > bal) {
rewardGuildBank.withdrawTo(_rewardToken, _to, bal);
} else {
rewardGuildBank.withdrawTo(_rewardToken, _to, _amount);
}
}
function getMultiplier(uint256 _from, uint256 _to) private pure returns (uint256) {
return _to - _from;
}
function _checkDeadAddress(address _wallet) internal pure returns (bool _res) {
assembly {
_res := true
if eq(_wallet, 0xdEaD) {
_res := false
}
}
}
function _checkZeroAddress(address _wallet) internal pure returns (bool _res) {
assembly {
_res := true
if eq(_wallet, 0x0) {
_res := false
}
}
}
}
文件 11 的 12:ReentrancyGuard.sol
pragma solidity ^0.8.20;
abstract contract ReentrancyGuard {
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
_status = ENTERED;
}
function _nonReentrantAfter() private {
_status = NOT_ENTERED;
}
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
文件 12 的 12:SafeERC20.sol
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Permit} from "./IERC20Permit.sol";
import {Address} from "./Address.sol";
library SafeERC20 {
using Address for address;
error SafeERC20FailedOperation(address token);
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
{
"compilationTarget": {
"LaunchPoolAccessControlERC20FundRaisingWithDelayedVesting.sol": "LaunchPoolAccessControlERC20FundRaisingWithDelayedVesting"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IERC20","name":"_stakingToken","type":"address"},{"internalType":"address[]","name":"admins","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AccessControlBadConfirmation","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"},{"internalType":"bytes32","name":"neededRole","type":"bytes32"}],"name":"AccessControlUnauthorizedAccount","type":"error"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"guildBank","type":"address"}],"name":"ContractDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FundRaisingClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Pledge","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PledgeFunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"}],"name":"PoolAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"address","name":"rewardToken","type":"address"}],"name":"PoolRewardTokenAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardEndTimestamp","type":"uint256"}],"name":"RewardsSetUp","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":"user","type":"address"},{"indexed":true,"internalType":"uint256","name":"pid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"ADMIN_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":[],"name":"FUND_MANAGEMENT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"POOL_MANAGEMENT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_TOKEN_ALLOCATION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"_rewardToken","type":"address"},{"internalType":"contract IERC20","name":"_fundRaisingToken","type":"address"},{"internalType":"uint256","name":"_tokenAllocationStartTimestamp","type":"uint256"},{"internalType":"uint256","name":"_stakingEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"_pledgeFundingEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"_targetRaise","type":"uint256"},{"internalType":"uint256","name":"_maxStakingAmountPerUser","type":"uint256"},{"internalType":"bool","name":"_withUpdate","type":"bool"}],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[]","name":"roles","type":"bytes32[]"},{"internalType":"address[]","name":"accounts","type":"address[]"}],"name":"batchGrantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"claimFundRaising","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"claimReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"fundPledge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"getPledgeFundingAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"getTotalRaisedVsTarget","outputs":[{"internalType":"uint256","name":"raised","type":"uint256"},{"internalType":"uint256","name":"target","type":"uint256"}],"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":[],"name":"massUpdatePools","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"numberOfPools","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"},{"internalType":"address","name":"_user","type":"address"}],"name":"pendingRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"pledge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToAccPercentagePerShare","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToAccRewardPerShareVesting","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToFundsClaimed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToLastPercentageAllocTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToLastRewardTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToMaxRewardTokensAvailableForVesting","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToRewardCliffEndTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToRewardEndTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToRewardPerTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToRewardStartTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToTotalFundedPercentageOfTargetRaise","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToTotalRaised","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolIdToTotalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"poolInfo","outputs":[{"internalType":"contract IERC20","name":"rewardToken","type":"address"},{"internalType":"contract IERC20","name":"fundRaisingToken","type":"address"},{"internalType":"uint256","name":"tokenAllocationStartTimestamp","type":"uint256"},{"internalType":"uint256","name":"stakingEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"pledgeFundingEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"targetRaise","type":"uint256"},{"internalType":"uint256","name":"maxStakingAmountPerUser","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"callerConfirmation","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":"rewardGuildBank","outputs":[{"internalType":"contract FundRaisingGuild","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"},{"internalType":"contract IERC20","name":"_rewardToken","type":"address"}],"name":"setRewardTokenOnPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"},{"internalType":"uint256","name":"_rewardAmount","type":"uint256"},{"internalType":"uint256","name":"_rewardStartTimestamp","type":"uint256"},{"internalType":"uint256","name":"_rewardCliffEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"_rewardEndTimestamp","type":"uint256"}],"name":"setupVestingRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"updatePool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"}],"name":"userInfo","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"pledgeFundingAmount","type":"uint256"},{"internalType":"uint256","name":"rewardDebtRewards","type":"uint256"},{"internalType":"uint256","name":"tokenAllocDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_pid","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]