文件 1 的 15: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);
_;
}
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 override returns (bool) {
return _roles[role].members[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(
string(
abi.encodePacked(
"AccessControl: account ",
Strings.toHexString(account),
" is missing role ",
Strings.toHexString(uint256(role), 32)
)
)
);
}
}
function getRoleAdmin(bytes32 role) public view virtual 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 的 15:CollectFees.sol
pragma solidity 0.8.9;
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "./interfaces/IFeeOracle.sol";
import "./IFeeCollector.sol";
abstract contract CollectFees is Ownable2Step {
address public feeCollectorAddress;
address public feeOracleAddress;
event SetFeeCollectorAddress(address newFeeCollectorAddress);
event SetFeeOracleAddress(address newFeeOracleAddress);
constructor(address feeCollectorAddress_, address feeOracleAddress_) {
feeCollectorAddress = feeCollectorAddress_;
feeOracleAddress = feeOracleAddress_;
}
function setFeeCollectorAddress(
address feeCollectorAddress_
) public onlyOwner {
feeCollectorAddress = feeCollectorAddress_;
emit SetFeeCollectorAddress(feeCollectorAddress_);
}
function setFeeOracleAddress(address feeOracleAddress_) public onlyOwner {
feeOracleAddress = feeOracleAddress_;
emit SetFeeOracleAddress(feeOracleAddress_);
}
modifier collectFee() {
uint256 fee = IFeeOracle(feeOracleAddress).getFee(msg.sender, msg.value);
require(msg.value >= fee, "Not enough funds");
IFeeCollector(feeCollectorAddress).receiveNative{value: fee}();
_;
}
function getFeeFromOracle(
address account_,
uint256 value_
) public view returns (uint256) {
uint256 fee = IFeeOracle(feeOracleAddress).getFee(account_, value_);
return fee;
}
}
文件 3 的 15: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 的 15:DualRewardsStaking.sol
pragma solidity 0.8.9;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "./interfaces/ICustomSwapRouter.sol";
import "./IFeeCollector.sol";
import "./CollectFees.sol";
contract DualRewardsStaking is AccessControl, CollectFees {
bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
struct Staker {
uint256 balance;
uint256 usdcRewardIndex;
uint256 xdcaRewardIndex;
uint256 pendingUsdcReward;
uint256 pendingXdcaReward;
}
struct StakerResponse {
uint256 balance;
uint256 pendingUsdcReward;
uint256 pendingXdcaReward;
uint256 claimableUsdcReward;
uint256 claimableXdcaReward;
}
struct DistributionSchedule {
uint256 startTime;
uint256 endTime;
uint256 amount;
}
IERC20 public stakingToken;
IERC20 public usdcRewardToken;
IERC20 public xdcaRewardToken;
ICustomSwapRouter public usdcToXdcaSwapRouter;
uint256 public totalStakedBalance;
uint256 public usdcLastDistributed;
uint256 public usdcGlobalRewardIndex;
uint256 public xdcaLastDistributed;
uint256 public xdcaGlobalRewardIndex;
uint256 private constant globalRewardIndexPrecision = 1e24;
DistributionSchedule public usdcRewardDistributionSchedule;
DistributionSchedule public xdcaRewardDistributionSchedule;
uint256 public feeOnClaiming;
mapping(address => Staker) public stakers;
event Stake(address tokensFrom, address indexed account, uint256 amount);
event Claim(
address indexed account,
uint256 usdcAmount,
uint256 xdcaAmount
);
event Compound(address indexed account, uint256 amount);
event Unstake(address indexed account, uint256 amount);
event Withdraw(address indexed account, uint256 amount);
event EmergencyWithdraw(IERC20 tokenToWithdraw, uint256 amountToWithdraw);
event SetUnbondingDuration(uint256 newUnbondingDuration);
event SetFeeOnClaiming(uint256 newFeeOnClaiming);
event SetUsdcDistributionSchedule(
DistributionSchedule newUsdcRewardDistributionSchedule
);
event SetXdcaDistributionSchedule(
DistributionSchedule newXdcaRewardDistributionSchedule
);
constructor(
ICustomSwapRouter usdcToXdcaSwapRouter_,
address feeCollectorAddress_,
address feeOracleAddress_,
IERC20 stakingToken_,
IERC20 usdcRewardToken_,
IERC20 xdcaRewardToken_,
uint256 feeOnClaiming_,
DistributionSchedule memory usdcRewardDistributionSchedule_,
DistributionSchedule memory xdcaRewardDistributionSchedule_
) CollectFees(feeCollectorAddress_, feeOracleAddress_) {
usdcToXdcaSwapRouter = usdcToXdcaSwapRouter_;
stakingToken = stakingToken_;
usdcRewardToken = usdcRewardToken_;
xdcaRewardToken = xdcaRewardToken_;
feeOnClaiming = feeOnClaiming_;
usdcRewardDistributionSchedule = usdcRewardDistributionSchedule_;
xdcaRewardDistributionSchedule = xdcaRewardDistributionSchedule_;
}
function grantOperator(address to) public onlyOwner {
_grantRole(OPERATOR_ROLE, to);
}
function revokeOperator(address to) public onlyOwner {
_revokeRole(OPERATOR_ROLE, to);
}
function setFeeOnClaiming(uint feeOnClaiming_) public onlyOwner {
feeOnClaiming = feeOnClaiming_;
emit SetFeeOnClaiming(feeOnClaiming_);
}
function setUsdcDistributionSchedule(
uint256 startTimestamp,
uint256 endTimestamp,
uint256 rewardToDistribute
) public onlyOwner {
require(startTimestamp < endTimestamp, "Invalid timestamps");
usdcGlobalRewardIndex = _computeUsdcReward();
usdcLastDistributed = block.timestamp;
usdcRewardDistributionSchedule = DistributionSchedule(
startTimestamp,
endTimestamp,
rewardToDistribute
);
emit SetUsdcDistributionSchedule(usdcRewardDistributionSchedule);
}
function setXdcaDistributionSchedule(
uint256 startTimestamp,
uint256 endTimestamp,
uint256 rewardToDistribute
) public onlyOwner {
require(startTimestamp < endTimestamp, "Invalid timestamps");
xdcaGlobalRewardIndex = _computeXdcaReward();
xdcaLastDistributed = block.timestamp;
xdcaRewardDistributionSchedule = DistributionSchedule(
startTimestamp,
endTimestamp,
rewardToDistribute
);
emit SetXdcaDistributionSchedule(xdcaRewardDistributionSchedule);
}
function emergencyWithdraw(
IERC20 tokenToWithdraw,
uint256 amountToWithdraw
) public onlyOwner {
if (address(tokenToWithdraw) == address(0)) {
address payable to = payable(msg.sender);
to.transfer(amountToWithdraw);
} else {
tokenToWithdraw.transfer(msg.sender, amountToWithdraw);
}
emit EmergencyWithdraw(tokenToWithdraw, amountToWithdraw);
}
function stakeForMany(
uint256[] calldata amounts,
address[] calldata users
) public onlyRole(OPERATOR_ROLE) {
require(
amounts.length == users.length,
"Amount and users length mismatch"
);
for (uint256 i = 0; i < amounts.length; i++) {
_stake(amounts[i], users[i], msg.sender);
}
}
function stakeFor(
uint256 amount,
address user
) public onlyRole(OPERATOR_ROLE) {
_stake(amount, user, msg.sender);
}
function stake(uint256 amount) public payable collectFee {
_stake(amount, msg.sender, msg.sender);
}
function claim() public payable collectFee {
Staker storage st = stakers[msg.sender];
_updateRewards();
(
uint256 pendingUsdcReward_,
uint256 pendingXdcaReward_
) = _computeStakerRewards(
msg.sender,
usdcGlobalRewardIndex,
xdcaGlobalRewardIndex
);
require(
pendingUsdcReward_ > 0 || pendingXdcaReward_ > 0,
"Nothing to claim"
);
st.usdcRewardIndex = usdcGlobalRewardIndex;
st.xdcaRewardIndex = xdcaGlobalRewardIndex;
st.pendingUsdcReward = 0;
st.pendingXdcaReward = 0;
(
uint256 claimableUsdcAmountToTransfer,
uint256 claimableXdcaAmountToTransfer
) = _computeClaimableRewards(pendingUsdcReward_, pendingXdcaReward_);
uint256 usdcFee = pendingUsdcReward_ - claimableUsdcAmountToTransfer;
uint256 xdcaFee = pendingXdcaReward_ - claimableXdcaAmountToTransfer;
if (usdcFee > 0) {
usdcRewardToken.approve(feeCollectorAddress, usdcFee);
IFeeCollector(feeCollectorAddress).receiveToken(
address(usdcRewardToken),
usdcFee
);
}
if (xdcaFee > 0) {
xdcaRewardToken.approve(feeCollectorAddress, xdcaFee);
IFeeCollector(feeCollectorAddress).receiveToken(
address(xdcaRewardToken),
xdcaFee
);
}
if (claimableUsdcAmountToTransfer > 0) {
usdcRewardToken.transfer(msg.sender, claimableUsdcAmountToTransfer);
}
if (claimableXdcaAmountToTransfer > 0) {
xdcaRewardToken.transfer(msg.sender, claimableXdcaAmountToTransfer);
}
emit Claim(
msg.sender,
claimableUsdcAmountToTransfer,
claimableXdcaAmountToTransfer
);
}
function compound(uint256 beliefPrice) public payable collectFee {
Staker storage st = stakers[msg.sender];
_updateRewards();
(
uint256 pendingUsdcReward_,
uint256 pendingXdcaReward_
) = _computeStakerRewards(
msg.sender,
usdcGlobalRewardIndex,
xdcaGlobalRewardIndex
);
require(
pendingUsdcReward_ > 0 || pendingXdcaReward_ > 0,
"Nothing to compound"
);
IERC20(usdcRewardToken).approve(
address(usdcToXdcaSwapRouter),
pendingUsdcReward_
);
uint256 stakingTokenAmountFromUsdcSwap = usdcToXdcaSwapRouter
.exchangeToken(pendingUsdcReward_, beliefPrice);
st.usdcRewardIndex = usdcGlobalRewardIndex;
st.xdcaRewardIndex = xdcaGlobalRewardIndex;
st.pendingUsdcReward = 0;
st.pendingXdcaReward = 0;
st.balance += stakingTokenAmountFromUsdcSwap + pendingXdcaReward_;
totalStakedBalance +=
stakingTokenAmountFromUsdcSwap +
pendingXdcaReward_;
emit Compound(
msg.sender,
stakingTokenAmountFromUsdcSwap + pendingXdcaReward_
);
}
function unstake(uint256 amountToUnstake) public payable collectFee {
Staker storage st = stakers[msg.sender];
require(st.balance > 0, "No staked tokens");
_updateRewards();
(
uint256 pendingUsdcReward_,
uint256 pendingXdcaReward_
) = _computeStakerRewards(
msg.sender,
usdcGlobalRewardIndex,
xdcaGlobalRewardIndex
);
require(
amountToUnstake <= st.balance,
"You cannot unstake more then you are staking"
);
st.usdcRewardIndex = usdcGlobalRewardIndex;
st.xdcaRewardIndex = xdcaGlobalRewardIndex;
st.pendingUsdcReward = pendingUsdcReward_;
st.pendingXdcaReward = pendingXdcaReward_;
st.balance -= amountToUnstake;
totalStakedBalance -= amountToUnstake;
stakingToken.transfer(msg.sender, amountToUnstake);
emit Unstake(msg.sender, amountToUnstake);
}
function getStaker(
address staker
) public view returns (StakerResponse memory) {
Staker memory st = stakers[staker];
uint256 _usdcGlobalRewardIndex = _computeUsdcReward();
uint256 _xdcaGlobalRewardIndex = _computeXdcaReward();
(
uint256 pendingUsdcReward_,
uint256 pendingXdcaReward_
) = _computeStakerRewards(
staker,
_usdcGlobalRewardIndex,
_xdcaGlobalRewardIndex
);
(
uint256 claimableUsdcReward_,
uint256 claimableXdcaReward_
) = _computeClaimableRewards(pendingUsdcReward_, pendingXdcaReward_);
StakerResponse memory stakerResponse = StakerResponse({
balance: st.balance,
pendingUsdcReward: pendingUsdcReward_,
pendingXdcaReward: pendingXdcaReward_,
claimableUsdcReward: claimableUsdcReward_,
claimableXdcaReward: claimableXdcaReward_
});
return stakerResponse;
}
function _updateRewards() internal {
usdcGlobalRewardIndex = _computeUsdcReward();
usdcLastDistributed = block.timestamp;
xdcaGlobalRewardIndex = _computeXdcaReward();
xdcaLastDistributed = block.timestamp;
}
function _computeClaimableRewards(
uint256 pendingUsdcReward,
uint256 pendingXdcaReward
) internal view returns (uint256, uint256) {
return (
((1e6 - feeOnClaiming) * pendingUsdcReward) / 1e6,
((1e6 - feeOnClaiming) * pendingXdcaReward) / 1e6
);
}
function _computeReward(
DistributionSchedule memory rewardDistributionSchedule,
uint256 globalRewardIndex,
uint256 lastDistributed
) internal view returns (uint256) {
if (totalStakedBalance == 0) {
return globalRewardIndex;
}
DistributionSchedule memory schedule = rewardDistributionSchedule;
if (
schedule.endTime < lastDistributed ||
schedule.startTime > block.timestamp
) {
return globalRewardIndex;
}
uint256 end = Math.min(schedule.endTime, block.timestamp);
uint256 start = Math.max(schedule.startTime, lastDistributed);
uint256 secondsFromLastDistribution = end - start;
uint256 totalSecondsInDistributionSchedule = schedule.endTime -
schedule.startTime;
uint256 rewardDistributedAmount = (schedule.amount *
secondsFromLastDistribution) / totalSecondsInDistributionSchedule;
return
globalRewardIndex +
((globalRewardIndexPrecision * rewardDistributedAmount) /
totalStakedBalance);
}
function _computeUsdcReward() internal view returns (uint256) {
return
_computeReward(
usdcRewardDistributionSchedule,
usdcGlobalRewardIndex,
usdcLastDistributed
);
}
function _computeXdcaReward() internal view returns (uint256) {
return
_computeReward(
xdcaRewardDistributionSchedule,
xdcaGlobalRewardIndex,
xdcaLastDistributed
);
}
function _computeStakerRewards(
address staker,
uint256 usdcGlobalRewardIndex_,
uint256 xdcaGlobalRewardIndex_
) internal view returns (uint256, uint256) {
uint256 pendingUsdcReward = stakers[staker].balance *
usdcGlobalRewardIndex_ -
stakers[staker].balance *
stakers[staker].usdcRewardIndex;
uint256 pendingXdcaReward = stakers[staker].balance *
xdcaGlobalRewardIndex_ -
stakers[staker].balance *
stakers[staker].xdcaRewardIndex;
return (
stakers[staker].pendingUsdcReward +
pendingUsdcReward /
globalRewardIndexPrecision,
stakers[staker].pendingXdcaReward +
pendingXdcaReward /
globalRewardIndexPrecision
);
}
function _stake(
uint256 amount,
address staker,
address transferTokensFrom
) internal {
require(amount > 0, "Amount must be greater than 0");
uint256 balance = stakingToken.balanceOf(transferTokensFrom);
require(amount <= balance, "Insufficient balance");
_updateRewards();
Staker storage st = stakers[staker];
(
uint256 pendingUsdcReward_,
uint256 pendingXdcaReward_
) = _computeStakerRewards(
msg.sender,
usdcGlobalRewardIndex,
xdcaGlobalRewardIndex
);
st.usdcRewardIndex = usdcGlobalRewardIndex;
st.xdcaRewardIndex = xdcaGlobalRewardIndex;
st.pendingUsdcReward = pendingUsdcReward_;
st.pendingXdcaReward = pendingXdcaReward_;
st.balance += amount;
totalStakedBalance += amount;
stakingToken.transferFrom(transferTokensFrom, address(this), amount);
emit Stake(transferTokensFrom, staker, amount);
}
}
文件 5 的 15: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;
}
}
文件 6 的 15: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 的 15:ICustomSwapRouter.sol
pragma solidity 0.8.9;
interface ICustomSwapRouter {
function exchangeToken(
uint256 amount,
uint256 beliefPrice
) external returns (uint256 amountOut);
}
文件 8 的 15:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 9 的 15: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);
}
文件 10 的 15:IFeeCollector.sol
pragma solidity ^0.8.9;
interface IFeeCollector {
function receiveNative() external payable;
function receiveToken(address tokenAddress, uint256 amount) external;
}
文件 11 的 15:IFeeOracle.sol
pragma solidity 0.8.9;
interface IFeeOracle {
function getFee(
address account,
uint256 value
) external view returns (uint256);
}
文件 12 的 15:Math.sol
pragma solidity ^0.8.0;
library Math {
enum Rounding {
Down,
Up,
Zero
}
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 == 0 ? 0 : (a - 1) / b + 1;
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
uint256 prod0;
uint256 prod1;
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
if (prod1 == 0) {
return prod0 / denominator;
}
require(denominator > prod1);
uint256 remainder;
assembly {
remainder := mulmod(x, y, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
uint256 twos = denominator & (~denominator + 1);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
prod0 |= prod1 * twos;
uint256 inverse = (3 * denominator) ^ 2;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
inverse *= 2 - denominator * inverse;
result = prod0 * inverse;
return result;
}
}
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 result = 1 << (log2(a) >> 1);
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
文件 13 的 15:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/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);
}
}
文件 14 的 15:Ownable2Step.sol
pragma solidity ^0.8.0;
import "./Ownable.sol";
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
function acceptOwnership() external {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
文件 15 的 15:Strings.sol
pragma solidity ^0.8.0;
import "./math/Math.sol";
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
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] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
{
"compilationTarget": {
"contracts/DualRewardsStaking.sol": "DualRewardsStaking"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"contract ICustomSwapRouter","name":"usdcToXdcaSwapRouter_","type":"address"},{"internalType":"address","name":"feeCollectorAddress_","type":"address"},{"internalType":"address","name":"feeOracleAddress_","type":"address"},{"internalType":"contract IERC20","name":"stakingToken_","type":"address"},{"internalType":"contract IERC20","name":"usdcRewardToken_","type":"address"},{"internalType":"contract IERC20","name":"xdcaRewardToken_","type":"address"},{"internalType":"uint256","name":"feeOnClaiming_","type":"uint256"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct DualRewardsStaking.DistributionSchedule","name":"usdcRewardDistributionSchedule_","type":"tuple"},{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct DualRewardsStaking.DistributionSchedule","name":"xdcaRewardDistributionSchedule_","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"usdcAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"xdcaAmount","type":"uint256"}],"name":"Claim","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Compound","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"contract IERC20","name":"tokenToWithdraw","type":"address"},{"indexed":false,"internalType":"uint256","name":"amountToWithdraw","type":"uint256"}],"name":"EmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","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":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":false,"internalType":"address","name":"newFeeCollectorAddress","type":"address"}],"name":"SetFeeCollectorAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newFeeOnClaiming","type":"uint256"}],"name":"SetFeeOnClaiming","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newFeeOracleAddress","type":"address"}],"name":"SetFeeOracleAddress","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newUnbondingDuration","type":"uint256"}],"name":"SetUnbondingDuration","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"indexed":false,"internalType":"struct DualRewardsStaking.DistributionSchedule","name":"newUsdcRewardDistributionSchedule","type":"tuple"}],"name":"SetUsdcDistributionSchedule","type":"event"},{"anonymous":false,"inputs":[{"components":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"indexed":false,"internalType":"struct DualRewardsStaking.DistributionSchedule","name":"newXdcaRewardDistributionSchedule","type":"tuple"}],"name":"SetXdcaDistributionSchedule","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"tokensFrom","type":"address"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Stake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstake","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPERATOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claim","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"beliefPrice","type":"uint256"}],"name":"compound","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenToWithdraw","type":"address"},{"internalType":"uint256","name":"amountToWithdraw","type":"uint256"}],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeCollectorAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeOnClaiming","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeOracleAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account_","type":"address"},{"internalType":"uint256","name":"value_","type":"uint256"}],"name":"getFeeFromOracle","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":"address","name":"staker","type":"address"}],"name":"getStaker","outputs":[{"components":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"pendingUsdcReward","type":"uint256"},{"internalType":"uint256","name":"pendingXdcaReward","type":"uint256"},{"internalType":"uint256","name":"claimableUsdcReward","type":"uint256"},{"internalType":"uint256","name":"claimableXdcaReward","type":"uint256"}],"internalType":"struct DualRewardsStaking.StakerResponse","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"grantOperator","outputs":[],"stateMutability":"nonpayable","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":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"revokeOperator","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":[{"internalType":"address","name":"feeCollectorAddress_","type":"address"}],"name":"setFeeCollectorAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"feeOnClaiming_","type":"uint256"}],"name":"setFeeOnClaiming","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"feeOracleAddress_","type":"address"}],"name":"setFeeOracleAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"rewardToDistribute","type":"uint256"}],"name":"setUsdcDistributionSchedule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"startTimestamp","type":"uint256"},{"internalType":"uint256","name":"endTimestamp","type":"uint256"},{"internalType":"uint256","name":"rewardToDistribute","type":"uint256"}],"name":"setXdcaDistributionSchedule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"stake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"user","type":"address"}],"name":"stakeFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"address[]","name":"users","type":"address[]"}],"name":"stakeForMany","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"stakers","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"usdcRewardIndex","type":"uint256"},{"internalType":"uint256","name":"xdcaRewardIndex","type":"uint256"},{"internalType":"uint256","name":"pendingUsdcReward","type":"uint256"},{"internalType":"uint256","name":"pendingXdcaReward","type":"uint256"}],"stateMutability":"view","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":[],"name":"totalStakedBalance","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":[{"internalType":"uint256","name":"amountToUnstake","type":"uint256"}],"name":"unstake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"usdcGlobalRewardIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdcLastDistributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdcRewardDistributionSchedule","outputs":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdcRewardToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdcToXdcaSwapRouter","outputs":[{"internalType":"contract ICustomSwapRouter","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"xdcaGlobalRewardIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"xdcaLastDistributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"xdcaRewardDistributionSchedule","outputs":[{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"xdcaRewardToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"}]