编译器
0.8.10+commit.fc410830
文件 1 的 12:Address.sol
pragma solidity ^0.8.1;
library Address {
function isContract(address account) internal view returns (bool) {
return account.code.length > 0;
}
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
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");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
if (returndata.length > 0) {
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
文件 2 的 12: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;
}
}
文件 3 的 12:Errors.sol
pragma solidity ^0.8.4;
library Errors {
error CallerNotAllowed();
error CallerNotManager();
error ZeroAddress();
error NullValue();
error InvalidValue();
error UnequalArraySizes();
error InvalidAddress();
error NullAmount();
error FailRewardUpdate();
error AlreadyRegistered();
error WardenNotOperator();
error NotRegistered();
error NotOfferOwner();
error NullPrice();
error NullMaxDuration();
error IncorrectExpiry();
error MaxPercTooHigh();
error MinPercOverMaxPerc();
error MinPercTooLow();
error PercentUnderMinRequired();
error PercentOverMax();
error DurationOverOfferMaxDuration();
error OfferExpired();
error DurationTooShort();
error PercentOutOfferBonds();
error LockEndTooShort();
error CannotDelegate();
error NullFees();
error FeesTooLow();
error FailDelegationBoost();
error CannotCancelBoost();
error NullClaimAmount();
error AmountTooHigh();
error ClaimBlocked();
error ClaimNotBlocked();
error InsufficientCash();
error InvalidBoostId();
error RewardsNotStarted();
error RewardsAlreadyStarted();
error BoostRewardsNull();
error RewardsNotUpdated();
error NotBoostBuyer();
error AlreadyClaimed();
error CannotClaim();
error InsufficientRewardCash();
error CannotWithdrawFeeToken();
error ReserveTooLow();
error BaseDropTooLow();
error MinDropTooHigh();
error AlreadyAllowedToken();
error NotAllowedToken();
error CannotRecoverToken();
error NotEnoughFees();
error FailBoostPurchase();
error CannotMatchOrder();
error EmptyArray();
error InvalidBoostOffer();
error TokenNotWhitelisted();
error RewardPerVoteTooLow();
error TargetVoteUnderMin();
error IncorrectMaxTotalRewardAmount();
error IncorrectMaxFeeAmount();
error InvalidEndTimestamp();
error NullEndTimestamp();
error NotPledgeCreator();
error ExpiredPledge();
error PledgeNotExpired();
error PledgeClosed();
error PledgeAlreadyClosed();
error TargetVotesTooLoow();
error RewardsPerVotesTooLow();
error InvalidPledgeID();
error TargetVotesOverflow();
error RewardsBalanceTooLow();
error InsufficientAllowance();
error EmptyBoost();
error NoRewards();
error NumberExceed64Bits();
}
文件 4 的 12:IBoostV2.sol
pragma solidity ^0.8.4;
interface IBoostV2 {
event Boost(
address indexed _from,
address indexed _to,
uint256 _bias,
uint256 _slope,
uint256 _start
);
function balanceOf(address _user) external view returns(uint256);
function allowance(address _user, address _spender) external view returns(uint256);
function adjusted_balance_of(address _user) external view returns(uint256);
function delegated_balance(address _user) external view returns(uint256);
function received_balance(address _user) external view returns(uint256);
function delegable_balance(address _user) external view returns(uint256);
function checkpoint_user(address _user) external;
function approve(address _spender, uint256 _value) external;
function boost(address _to, uint256 _amount, uint256 _endtime, address _from) external;
}
文件 5 的 12:IERC20.sol
pragma solidity ^0.8.0;
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 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 from,
address to,
uint256 amount
) external returns (bool);
}
文件 6 的 12:IVotingEscrow.sol
pragma solidity ^0.8.4;
interface IVotingEscrow {
struct LockedBalance {
int128 amount;
uint256 end;
}
function balanceOf(address _account) external view returns (uint256);
function locked(address _account) external view returns (LockedBalance memory);
function create_lock(uint256 _value, uint256 _unlock_time) external returns (uint256);
function increase_amount(uint256 _value) external;
function increase_unlock_time(uint256 _unlock_time) external;
function locked__end(address _addr) external view returns (uint256);
function get_last_user_slope(address _addr) external view returns (uint256);
}
文件 7 的 12:Ownable.sol
pragma solidity ^0.8.0;
import "./Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 8 的 12:Owner.sol
pragma solidity 0.8.10;
import "../oz/utils/Ownable.sol";
contract Owner is Ownable {
address public pendingOwner;
event NewPendingOwner(address indexed previousPendingOwner, address indexed newPendingOwner);
error CannotBeOwner();
error CallerNotPendingOwner();
error OwnerZeroAddress();
function transferOwnership(address newOwner) public override virtual onlyOwner {
if(newOwner == address(0)) revert OwnerZeroAddress();
if(newOwner == owner()) revert CannotBeOwner();
address oldPendingOwner = pendingOwner;
pendingOwner = newOwner;
emit NewPendingOwner(oldPendingOwner, newOwner);
}
function acceptOwnership() public virtual {
if(msg.sender != pendingOwner) revert CallerNotPendingOwner();
address newOwner = pendingOwner;
_transferOwnership(pendingOwner);
pendingOwner = address(0);
emit NewPendingOwner(newOwner, address(0));
}
}
文件 9 的 12:Pausable.sol
pragma solidity ^0.8.0;
import "./Context.sol";
abstract contract Pausable is Context {
event Paused(address account);
event Unpaused(address account);
bool private _paused;
constructor() {
_paused = false;
}
modifier whenNotPaused() {
_requireNotPaused();
_;
}
modifier whenPaused() {
_requirePaused();
_;
}
function paused() public view virtual returns (bool) {
return _paused;
}
function _requireNotPaused() internal view virtual {
require(!paused(), "Pausable: paused");
}
function _requirePaused() internal view virtual {
require(paused(), "Pausable: not paused");
}
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
文件 10 的 12:ReentrancyGuard.sol
pragma solidity ^0.8.0;
abstract contract ReentrancyGuard {
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
modifier nonReentrant() {
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
_status = _ENTERED;
_;
_status = _NOT_ENTERED;
}
}
文件 11 的 12:SafeERC20.sol
pragma solidity ^0.8.0;
import "../interfaces/IERC20.sol";
import "../utils/Address.sol";
library SafeERC20 {
using Address for address;
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));
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function _callOptionalReturn(IERC20 token, bytes memory data) private {
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
文件 12 的 12:WardenPledge.sol
pragma solidity 0.8.10;
import "./oz/interfaces/IERC20.sol";
import "./oz/libraries/SafeERC20.sol";
import "./utils/Owner.sol";
import "./oz/utils/Pausable.sol";
import "./oz/utils/ReentrancyGuard.sol";
import "./interfaces/IVotingEscrow.sol";
import "./interfaces/IBoostV2.sol";
import "./utils/Errors.sol";
contract WardenPledge is Owner, Pausable, ReentrancyGuard {
using SafeERC20 for IERC20;
uint256 public constant UNIT = 1e18;
uint256 public constant MAX_PCT = 10000;
uint256 public constant WEEK = 604800;
uint256 public constant MIN_PLEDGE_DURATION = 1 weeks;
uint256 public constant MIN_DELEGATION_DURATION = 2 days;
struct Pledge{
uint256 targetVotes;
uint256 rewardPerVotePerWeek;
address receiver;
address rewardToken;
uint64 endTimestamp;
bool closed;
}
Pledge[] public pledges;
mapping(uint256 => address) public pledgeOwner;
mapping(address => uint256[]) public ownerPledges;
mapping(uint256 => uint256) public pledgeAvailableRewardAmounts;
IVotingEscrow public immutable votingEscrow;
IBoostV2 public immutable delegationBoost;
mapping(address => uint256) public minAmountRewardToken;
mapping(address => uint256) public rewardTokenTotalAmount;
uint256 public protocolFeeRatio = 500;
address public chestAddress;
uint256 public minVoteDiff;
event NewPledge(
address indexed creator,
address indexed receiver,
address indexed rewardToken,
uint256 id,
uint256 targetVotes,
uint256 rewardPerVotePerWeek,
uint256 endTimestamp
);
event ExtendPledgeDuration(uint256 indexed pledgeId, uint256 oldEndTimestamp, uint256 newEndTimestamp);
event IncreasePledgeRewardPerVote(uint256 indexed pledgeId, uint256 oldrewardPerVotePerWeek, uint256 newrewardPerVotePerWeek);
event ClosePledge(uint256 indexed pledgeId);
event RetrievedPledgeRewards(uint256 indexed pledgeId, address receiver, uint256 amount);
event Pledged(uint256 indexed pledgeId, address indexed user, uint256 amount, uint256 endTimestamp);
event NewRewardToken(address indexed token, uint256 minRewardPerWeek);
event UpdateRewardToken(address indexed token, uint256 minRewardPerWeek);
event RemoveRewardToken(address indexed token);
event ChestUpdated(address oldChest, address newChest);
event PlatformFeeUpdated(uint256 oldFee, uint256 newFee);
event MinVoteDiffUpdated(uint256 oldMinVoteDiff, uint256 newMinVoteDiff);
constructor(
address _votingEscrow,
address _delegationBoost,
address _chestAddress,
uint256 _minVoteDiff
) {
if(_minVoteDiff < UNIT) revert Errors.InvalidValue();
if(_votingEscrow == address(0) || _delegationBoost == address(0) || _chestAddress == address(0)) revert Errors.ZeroAddress();
votingEscrow = IVotingEscrow(_votingEscrow);
delegationBoost = IBoostV2(_delegationBoost);
chestAddress = _chestAddress;
minVoteDiff = _minVoteDiff;
}
function nextPledgeIndex() public view returns(uint256){
return pledges.length;
}
function getUserPledges(address user) external view returns(uint256[] memory){
return ownerPledges[user];
}
function getAllPledges() external view returns(Pledge[] memory){
return pledges;
}
function _getRoundedTimestamp(uint256 timestamp) internal pure returns(uint256) {
return (timestamp / WEEK) * WEEK;
}
function pledge(uint256 pledgeId, uint256 amount, uint256 endTimestamp) external nonReentrant whenNotPaused {
_pledge(pledgeId, msg.sender, amount, endTimestamp);
}
function pledgePercent(uint256 pledgeId, uint256 percent, uint256 endTimestamp) external nonReentrant whenNotPaused {
if(percent > MAX_PCT) revert Errors.PercentOverMax();
uint256 amount = (delegationBoost.delegable_balance(msg.sender) * percent) / MAX_PCT;
_pledge(pledgeId, msg.sender, amount, endTimestamp);
}
function _pledge(uint256 pledgeId, address user, uint256 amount, uint256 endTimestamp) internal {
if(amount == 0) revert Errors.NullValue();
if(pledgeId >= pledges.length) revert Errors.InvalidPledgeID();
Pledge storage pledgeParams = pledges[pledgeId];
if(pledgeParams.closed) revert Errors.PledgeClosed();
if(pledgeParams.endTimestamp <= block.timestamp) revert Errors.ExpiredPledge();
if(endTimestamp == 0) endTimestamp = pledgeParams.endTimestamp;
if(endTimestamp > pledgeParams.endTimestamp || endTimestamp != _getRoundedTimestamp(endTimestamp)) revert Errors.InvalidEndTimestamp();
uint256 boostDuration = endTimestamp - block.timestamp;
if(boostDuration < MIN_DELEGATION_DURATION) revert Errors.DurationTooShort();
IBoostV2 _delegationBoost = delegationBoost;
_delegationBoost.checkpoint_user(user);
if(_delegationBoost.allowance(user, address(this)) < amount) revert Errors.InsufficientAllowance();
if(_delegationBoost.delegable_balance(user) < amount) revert Errors.CannotDelegate();
if(_delegationBoost.adjusted_balance_of(pledgeParams.receiver) + amount > pledgeParams.targetVotes) revert Errors.TargetVotesOverflow();
_delegationBoost.boost(
pledgeParams.receiver,
amount,
endTimestamp,
user
);
uint256 slope = amount / boostDuration;
uint256 bias = slope * boostDuration;
if(bias == 0) revert Errors.EmptyBoost();
uint256 totalDelegatedAmount = ((bias * boostDuration) + bias) / 2;
uint256 rewardAmount = ((totalDelegatedAmount * pledgeParams.rewardPerVotePerWeek) / WEEK) / UNIT;
if(rewardAmount == 0) revert Errors.NoRewards();
address _rewardToken = pledgeParams.rewardToken;
if(rewardAmount > pledgeAvailableRewardAmounts[pledgeId]) revert Errors.RewardsBalanceTooLow();
pledgeAvailableRewardAmounts[pledgeId] -= rewardAmount;
rewardTokenTotalAmount[_rewardToken] -= rewardAmount;
uint256 feeAmount = (rewardAmount * protocolFeeRatio) / MAX_PCT;
rewardAmount = rewardAmount - feeAmount;
IERC20(_rewardToken).safeTransfer(chestAddress, feeAmount);
IERC20(_rewardToken).safeTransfer(user, rewardAmount);
emit Pledged(pledgeId, user, bias, endTimestamp);
}
struct CreatePledgeVars {
uint256 totalVotes;
uint256 totalRewardAmount;
uint256 newPledgeID;
}
function createPledge(
address receiver,
address rewardToken,
uint256 targetVotes,
uint256 rewardPerVotePerWeek,
uint256 endTimestamp,
uint256 maxTotalRewardAmount
) external nonReentrant whenNotPaused returns(uint256){
address creator = msg.sender;
if(receiver == address(0) || rewardToken == address(0)) revert Errors.ZeroAddress();
uint256 _minAmountRewardToken = minAmountRewardToken[rewardToken];
if(_minAmountRewardToken == 0) revert Errors.TokenNotWhitelisted();
if(rewardPerVotePerWeek < _minAmountRewardToken) revert Errors.RewardPerVoteTooLow();
if(endTimestamp == 0) revert Errors.NullEndTimestamp();
if(endTimestamp != _getRoundedTimestamp(endTimestamp) || endTimestamp < block.timestamp) revert Errors.InvalidEndTimestamp();
CreatePledgeVars memory vars;
vars.totalVotes = _getTotalVotesForDuration(receiver, targetVotes, endTimestamp);
vars.totalRewardAmount = ((rewardPerVotePerWeek * vars.totalVotes) / WEEK) / UNIT;
if(vars.totalRewardAmount == 0) revert Errors.NullAmount();
if(vars.totalRewardAmount > maxTotalRewardAmount) revert Errors.IncorrectMaxTotalRewardAmount();
IERC20(rewardToken).safeTransferFrom(creator, address(this), vars.totalRewardAmount);
vars.newPledgeID = pledges.length;
pledgeAvailableRewardAmounts[vars.newPledgeID] = vars.totalRewardAmount;
rewardTokenTotalAmount[rewardToken] += vars.totalRewardAmount;
pledges.push(Pledge(
targetVotes,
rewardPerVotePerWeek,
receiver,
rewardToken,
safe64(endTimestamp),
false
));
pledgeOwner[vars.newPledgeID] = creator;
ownerPledges[creator].push(vars.newPledgeID);
emit NewPledge(creator, receiver, rewardToken, vars.newPledgeID, targetVotes, rewardPerVotePerWeek, endTimestamp);
return vars.newPledgeID;
}
function extendPledge(
uint256 pledgeId,
uint256 newEndTimestamp,
uint256 maxTotalRewardAmount
) external nonReentrant whenNotPaused {
if(pledgeId >= pledges.length) revert Errors.InvalidPledgeID();
address creator = pledgeOwner[pledgeId];
if(msg.sender != creator) revert Errors.NotPledgeCreator();
Pledge storage pledgeParams = pledges[pledgeId];
uint256 oldEndTimestamp = pledgeParams.endTimestamp;
if(pledgeParams.closed) revert Errors.PledgeClosed();
if(oldEndTimestamp <= block.timestamp) revert Errors.ExpiredPledge();
address _rewardToken = pledgeParams.rewardToken;
if(minAmountRewardToken[_rewardToken] == 0) revert Errors.TokenNotWhitelisted();
if(pledgeParams.rewardPerVotePerWeek < minAmountRewardToken[_rewardToken]) revert Errors.RewardPerVoteTooLow();
if(newEndTimestamp == 0) revert Errors.NullEndTimestamp();
if(newEndTimestamp != _getRoundedTimestamp(newEndTimestamp) || newEndTimestamp < oldEndTimestamp) revert Errors.InvalidEndTimestamp();
if((newEndTimestamp - oldEndTimestamp) < MIN_PLEDGE_DURATION) revert Errors.DurationTooShort();
uint256 oldEndTotalRemaingVotes = _getTotalVotesForDuration(pledgeParams.receiver, pledgeParams.targetVotes, oldEndTimestamp);
uint256 totalVotesAddedDuration = _getTotalVotesForDuration(pledgeParams.receiver, pledgeParams.targetVotes, newEndTimestamp) - oldEndTotalRemaingVotes;
uint256 totalRewardAmount = ((pledgeParams.rewardPerVotePerWeek * totalVotesAddedDuration) / WEEK) / UNIT;
if(totalRewardAmount == 0) revert Errors.NullAmount();
if(totalRewardAmount > maxTotalRewardAmount) revert Errors.IncorrectMaxTotalRewardAmount();
IERC20(_rewardToken).safeTransferFrom(creator, address(this), totalRewardAmount);
pledgeParams.endTimestamp = safe64(newEndTimestamp);
pledgeAvailableRewardAmounts[pledgeId] += totalRewardAmount;
rewardTokenTotalAmount[_rewardToken] += totalRewardAmount;
emit ExtendPledgeDuration(pledgeId, oldEndTimestamp, newEndTimestamp);
}
function increasePledgeRewardPerVote(
uint256 pledgeId,
uint256 newRewardPerVotePerWeek,
uint256 maxTotalRewardAmount
) external nonReentrant whenNotPaused {
if(pledgeId >= pledges.length) revert Errors.InvalidPledgeID();
address creator = pledgeOwner[pledgeId];
if(msg.sender != creator) revert Errors.NotPledgeCreator();
Pledge storage pledgeParams = pledges[pledgeId];
if(pledgeParams.closed) revert Errors.PledgeClosed();
uint256 _endTimestamp = pledgeParams.endTimestamp;
if(_endTimestamp <= block.timestamp) revert Errors.ExpiredPledge();
address _rewardToken = pledgeParams.rewardToken;
if(minAmountRewardToken[_rewardToken] == 0) revert Errors.TokenNotWhitelisted();
if(pledgeParams.rewardPerVotePerWeek < minAmountRewardToken[_rewardToken]) revert Errors.RewardPerVoteTooLow();
uint256 oldRewardPerVotePerWeek = pledgeParams.rewardPerVotePerWeek;
if(newRewardPerVotePerWeek <= oldRewardPerVotePerWeek) revert Errors.RewardsPerVotesTooLow();
uint256 rewardPerVoteDiff = newRewardPerVotePerWeek - oldRewardPerVotePerWeek;
uint256 totalVotes = _getTotalVotesForDuration(pledgeParams.receiver, pledgeParams.targetVotes, _endTimestamp);
uint256 totalRewardAmount = ((rewardPerVoteDiff * totalVotes) / WEEK) / UNIT;
if(totalRewardAmount == 0) revert Errors.NullAmount();
if(totalRewardAmount > maxTotalRewardAmount) revert Errors.IncorrectMaxTotalRewardAmount();
IERC20(_rewardToken).safeTransferFrom(creator, address(this), totalRewardAmount);
pledgeParams.rewardPerVotePerWeek = newRewardPerVotePerWeek;
pledgeAvailableRewardAmounts[pledgeId] += totalRewardAmount;
rewardTokenTotalAmount[_rewardToken] += totalRewardAmount;
emit IncreasePledgeRewardPerVote(pledgeId, oldRewardPerVotePerWeek, newRewardPerVotePerWeek);
}
function closePledge(uint256 pledgeId, address receiver) external nonReentrant {
if(receiver == address(0) || receiver == address(this)) revert Errors.InvalidValue();
if(pledgeId >= pledges.length) revert Errors.InvalidPledgeID();
address creator = pledgeOwner[pledgeId];
if(msg.sender != creator) revert Errors.NotPledgeCreator();
Pledge storage pledgeParams = pledges[pledgeId];
if(pledgeParams.closed) revert Errors.PledgeAlreadyClosed();
pledgeParams.closed = true;
uint256 remainingAmount = pledgeAvailableRewardAmounts[pledgeId];
if(remainingAmount != 0) {
pledgeAvailableRewardAmounts[pledgeId] = 0;
address _rewardToken = pledgeParams.rewardToken;
rewardTokenTotalAmount[_rewardToken] -= remainingAmount;
IERC20(_rewardToken).safeTransfer(receiver, remainingAmount);
emit RetrievedPledgeRewards(pledgeId, receiver, remainingAmount);
}
emit ClosePledge(pledgeId);
}
function _getTotalVotesForDuration(
address receiver,
uint256 targetVotes,
uint256 endTimestamp
) internal view returns(uint256) {
IVotingEscrow _votingEscrow = votingEscrow;
uint256 duration = endTimestamp - block.timestamp;
if(duration < MIN_PLEDGE_DURATION) revert Errors.DurationTooShort();
uint256 neededTotalVotes = targetVotes * duration;
uint256 totalReceiverVotes;
uint256 receiverBalance = _votingEscrow.balanceOf(receiver);
if((targetVotes - receiverBalance) < minVoteDiff) revert Errors.TargetVoteUnderMin();
if(receiverBalance != 0){
uint256 receiverLockEnd = _votingEscrow.locked__end(receiver);
if(receiverLockEnd < endTimestamp) {
uint256 lockRemainingDuration = receiverLockEnd - block.timestamp;
totalReceiverVotes = ((receiverBalance * lockRemainingDuration) + receiverBalance) / 2;
} else {
uint256 receiverSlope = _votingEscrow.get_last_user_slope(receiver);
uint256 receiverEndTsBias = receiverBalance - (duration * receiverSlope);
totalReceiverVotes = ((duration * (receiverBalance + receiverEndTsBias + receiverSlope)) / 2);
}
}
return neededTotalVotes - totalReceiverVotes;
}
function _addRewardToken(address token, uint256 minRewardPerWeek) internal {
if(token == address(0)) revert Errors.ZeroAddress();
if(minRewardPerWeek == 0) revert Errors.NullValue();
if(minAmountRewardToken[token] != 0) revert Errors.AlreadyAllowedToken();
minAmountRewardToken[token] = minRewardPerWeek;
emit NewRewardToken(token, minRewardPerWeek);
}
function addMultipleRewardToken(address[] calldata tokens, uint256[] calldata minRewardsPerWeek) external onlyOwner {
uint256 length = tokens.length;
if(length == 0) revert Errors.EmptyArray();
if(length != minRewardsPerWeek.length) revert Errors.UnequalArraySizes();
for(uint256 i; i < length;){
_addRewardToken(tokens[i], minRewardsPerWeek[i]);
unchecked{ ++i; }
}
}
function addRewardToken(address token, uint256 minRewardPerWeek) external onlyOwner {
_addRewardToken(token, minRewardPerWeek);
}
function updateRewardToken(address token, uint256 minRewardPerWeek) external onlyOwner {
if(token == address(0)) revert Errors.ZeroAddress();
if(minAmountRewardToken[token] == 0) revert Errors.NotAllowedToken();
if(minRewardPerWeek == 0) revert Errors.InvalidValue();
minAmountRewardToken[token] = minRewardPerWeek;
emit UpdateRewardToken(token, minRewardPerWeek);
}
function removeRewardToken(address token) external onlyOwner {
if(token == address(0)) revert Errors.ZeroAddress();
if(minAmountRewardToken[token] == 0) revert Errors.NotAllowedToken();
minAmountRewardToken[token] = 0;
emit RemoveRewardToken(token);
}
function updateChest(address chest) external onlyOwner {
if(chest == address(0) || chest == address(this)) revert Errors.InvalidAddress();
address oldChest = chestAddress;
chestAddress = chest;
emit ChestUpdated(oldChest, chest);
}
function updateMinVoteDiff(uint256 newMinVoteDiff) external onlyOwner {
if(newMinVoteDiff < UNIT) revert Errors.InvalidValue();
uint256 oldMinTarget = minVoteDiff;
minVoteDiff = newMinVoteDiff;
emit MinVoteDiffUpdated(oldMinTarget, newMinVoteDiff);
}
function updatePlatformFee(uint256 newFee) external onlyOwner {
if(newFee > 500 || newFee == 0) revert Errors.InvalidValue();
uint256 oldFee = protocolFeeRatio;
protocolFeeRatio = newFee;
emit PlatformFeeUpdated(oldFee, newFee);
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
function recoverERC20(address token) external nonReentrant onlyOwner returns(bool) {
uint256 currentBalance = IERC20(token).balanceOf(address(this));
if(currentBalance == 0) revert Errors.NullValue();
uint256 amount;
uint256 pledgesBalance = rewardTokenTotalAmount[token];
if(pledgesBalance >= currentBalance) revert Errors.CannotRecoverToken();
amount = currentBalance - pledgesBalance;
if(amount != 0) {
IERC20(token).safeTransfer(owner(), amount);
}
return true;
}
function safe64(uint256 n) internal pure returns (uint64) {
if(n > type(uint64).max) revert Errors.NumberExceed64Bits();
return uint64(n);
}
}
{
"compilationTarget": {
"contracts/WardenPledge.sol": "WardenPledge"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_votingEscrow","type":"address"},{"internalType":"address","name":"_delegationBoost","type":"address"},{"internalType":"address","name":"_chestAddress","type":"address"},{"internalType":"uint256","name":"_minVoteDiff","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyAllowedToken","type":"error"},{"inputs":[],"name":"CallerNotPendingOwner","type":"error"},{"inputs":[],"name":"CannotBeOwner","type":"error"},{"inputs":[],"name":"CannotDelegate","type":"error"},{"inputs":[],"name":"CannotRecoverToken","type":"error"},{"inputs":[],"name":"DurationTooShort","type":"error"},{"inputs":[],"name":"EmptyArray","type":"error"},{"inputs":[],"name":"EmptyBoost","type":"error"},{"inputs":[],"name":"ExpiredPledge","type":"error"},{"inputs":[],"name":"IncorrectMaxTotalRewardAmount","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidEndTimestamp","type":"error"},{"inputs":[],"name":"InvalidPledgeID","type":"error"},{"inputs":[],"name":"InvalidValue","type":"error"},{"inputs":[],"name":"NoRewards","type":"error"},{"inputs":[],"name":"NotAllowedToken","type":"error"},{"inputs":[],"name":"NotPledgeCreator","type":"error"},{"inputs":[],"name":"NullAmount","type":"error"},{"inputs":[],"name":"NullEndTimestamp","type":"error"},{"inputs":[],"name":"NullValue","type":"error"},{"inputs":[],"name":"NumberExceed64Bits","type":"error"},{"inputs":[],"name":"OwnerZeroAddress","type":"error"},{"inputs":[],"name":"PercentOverMax","type":"error"},{"inputs":[],"name":"PledgeAlreadyClosed","type":"error"},{"inputs":[],"name":"PledgeClosed","type":"error"},{"inputs":[],"name":"RewardPerVoteTooLow","type":"error"},{"inputs":[],"name":"RewardsBalanceTooLow","type":"error"},{"inputs":[],"name":"RewardsPerVotesTooLow","type":"error"},{"inputs":[],"name":"TargetVoteUnderMin","type":"error"},{"inputs":[],"name":"TargetVotesOverflow","type":"error"},{"inputs":[],"name":"TokenNotWhitelisted","type":"error"},{"inputs":[],"name":"UnequalArraySizes","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldChest","type":"address"},{"indexed":false,"internalType":"address","name":"newChest","type":"address"}],"name":"ChestUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pledgeId","type":"uint256"}],"name":"ClosePledge","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pledgeId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldEndTimestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newEndTimestamp","type":"uint256"}],"name":"ExtendPledgeDuration","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pledgeId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"oldrewardPerVotePerWeek","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newrewardPerVotePerWeek","type":"uint256"}],"name":"IncreasePledgeRewardPerVote","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldMinVoteDiff","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newMinVoteDiff","type":"uint256"}],"name":"MinVoteDiffUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousPendingOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newPendingOwner","type":"address"}],"name":"NewPendingOwner","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"rewardToken","type":"address"},{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetVotes","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"rewardPerVotePerWeek","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"NewPledge","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"minRewardPerWeek","type":"uint256"}],"name":"NewRewardToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"PlatformFeeUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pledgeId","type":"uint256"},{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"Pledged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"}],"name":"RemoveRewardToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"pledgeId","type":"uint256"},{"indexed":false,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RetrievedPledgeRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"minRewardPerWeek","type":"uint256"}],"name":"UpdateRewardToken","type":"event"},{"inputs":[],"name":"MAX_PCT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_DELEGATION_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_PLEDGE_DURATION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNIT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WEEK","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"minRewardsPerWeek","type":"uint256[]"}],"name":"addMultipleRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"minRewardPerWeek","type":"uint256"}],"name":"addRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chestAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pledgeId","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"closePledge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint256","name":"targetVotes","type":"uint256"},{"internalType":"uint256","name":"rewardPerVotePerWeek","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"maxTotalRewardAmount","type":"uint256"}],"name":"createPledge","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"delegationBoost","outputs":[{"internalType":"contract IBoostV2","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pledgeId","type":"uint256"},{"internalType":"uint256","name":"newEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"maxTotalRewardAmount","type":"uint256"}],"name":"extendPledge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getAllPledges","outputs":[{"components":[{"internalType":"uint256","name":"targetVotes","type":"uint256"},{"internalType":"uint256","name":"rewardPerVotePerWeek","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint64","name":"endTimestamp","type":"uint64"},{"internalType":"bool","name":"closed","type":"bool"}],"internalType":"struct WardenPledge.Pledge[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserPledges","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pledgeId","type":"uint256"},{"internalType":"uint256","name":"newRewardPerVotePerWeek","type":"uint256"},{"internalType":"uint256","name":"maxTotalRewardAmount","type":"uint256"}],"name":"increasePledgeRewardPerVote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"minAmountRewardToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minVoteDiff","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextPledgeIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerPledges","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pledgeId","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"pledge","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pledgeAvailableRewardAmounts","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pledgeOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"pledgeId","type":"uint256"},{"internalType":"uint256","name":"percent","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"}],"name":"pledgePercent","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"pledges","outputs":[{"internalType":"uint256","name":"targetVotes","type":"uint256"},{"internalType":"uint256","name":"rewardPerVotePerWeek","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"rewardToken","type":"address"},{"internalType":"uint64","name":"endTimestamp","type":"uint64"},{"internalType":"bool","name":"closed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFeeRatio","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"recoverERC20","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"removeRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"rewardTokenTotalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"chest","type":"address"}],"name":"updateChest","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newMinVoteDiff","type":"uint256"}],"name":"updateMinVoteDiff","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newFee","type":"uint256"}],"name":"updatePlatformFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"minRewardPerWeek","type":"uint256"}],"name":"updateRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"votingEscrow","outputs":[{"internalType":"contract IVotingEscrow","name":"","type":"address"}],"stateMutability":"view","type":"function"}]