diff --git a/src/ChefIncentivesController/ChefIncentivesMultiRewarder.sol b/src/ChefIncentivesController/ChefIncentivesMultiRewarder.sol index 01f7a51..af516a4 100644 --- a/src/ChefIncentivesController/ChefIncentivesMultiRewarder.sol +++ b/src/ChefIncentivesController/ChefIncentivesMultiRewarder.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { SafeMath} from "../dependencies/openzeppelin/contracts/SafeMath.sol"; +import { IERC20 } from "../dependencies/openzeppelin/contracts/IERC20.sol"; contract ChefIncentivesMultiRewarder { diff --git a/src/MasterChef/MasterChefMultiRewarder.sol b/src/MasterChef/MasterChefMultiRewarder.sol new file mode 100644 index 0000000..178b6e0 --- /dev/null +++ b/src/MasterChef/MasterChefMultiRewarder.sol @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +import { SafeMath} from "../dependencies/openzeppelin/contracts/SafeMath.sol"; +import { IERC20 } from "../dependencies/openzeppelin/contracts/IERC20.sol"; + +contract MasterchefMultiRewards { + + using SafeMath for uint256; + + address multiIncentiveAdmin; + + // aToken address => total staked amount + mapping(address => uint256) public multiTotalStaked; + // aToken address => staked amount + mapping(address => mapping(address => uint256)) public multiUserStaked; + // aToken address => rewardToken array + mapping(address => address[]) public multiRewardTokens; + // rewardToken address => isMultiRewardToken + mapping(address => bool) public isMultiRewardToken; + // aTokenAddress => rewardTokenAddress => multiRewardPerToken + mapping(address => mapping(address => uint256)) public multiRewardPerToken; + // user address => aToken address => reward address => multiRewardPerTokenOffsetScaled + mapping(address => mapping(address => mapping(address => uint256))) + public multiUserRewardOffset; + // user address => rewardToken address => accumulated rewards + mapping(address => mapping(address => uint256)) public multiUserPendingRewards; + // aToken address => rewardToken address => rewardPerSecond + mapping(address => mapping(address => uint256)) public multiRewardPerSecond; + // aToken address => rewardToken address => lastMultiUpdateTime + mapping(address => mapping(address => uint256)) public lastMultiUpdateTime; + + uint256 internal constant SCALE = 1e24; + + event multiStakeRecorded(address user, address aToken, uint256 amount); + event multiUnstakeRecorded(address user, address aToken, uint256 amount); + event multiRewardHarvested( + address user, + address aToken, + address rewardToken, + uint256 amount + ); + event multiRewardAdded( + address aToken, + address rewardToken, + uint256 rewardsPerSecond + ); + event multiRewardRemoved(address aToken, address rewardToken); + event multiRewardUpdated( + address aToken, + address rewardToken, + uint256 rewardsPerSecond + ); + event multiRewardDeposited(address rewardToken, uint256 amount); + event multiRewardWithdrawn(address rewardToken, uint256 amount); + + modifier onlyIncentiveAdmin() { + require(msg.sender == multiIncentiveAdmin, "caller is not the multiIncentiveAdmin"); + _; + } + + constructor() { + multiIncentiveAdmin = msg.sender; + } + + // ╒═════════════════════✰° + // USER FUNCTIONS + // °✰════════════════════╛ + + // Stake aToken + function stakeIncentiveTokens(address _aToken, uint256 _amount) internal { + require(_amount > 0, "amount must be greater than 0"); + calculateUserPending(msg.sender, _aToken); + // Update rewards before modifying stakes to ensure proper accounting + updateMultiRewardAccounting(_aToken); + + multiTotalStaked[_aToken] = multiTotalStaked[_aToken].add(_amount); + multiUserStaked[msg.sender][_aToken] = multiUserStaked[msg.sender][_aToken].add(_amount); + + address[] memory rewardTokenList = multiRewardTokens[_aToken]; + uint256 rewardTokenCount = rewardTokenList.length; + + for (uint256 i = 0; i < rewardTokenCount; i++) { + address rewardToken = rewardTokenList[i]; + // Set the offset to current rewardPerToken to start counting from this point + multiUserRewardOffset[msg.sender][_aToken][rewardToken] = + _simulateRewardPerToken(_aToken, rewardToken, multiTotalStaked[_aToken].sub(_amount)); + } + + emit multiStakeRecorded(msg.sender, _aToken, _amount); + } + + // Withdraw aToken + function _unstakeIncentiveTokens(address _aToken, uint256 _amount) internal { + require(_amount > 0, "amount must be greater than 0"); + // assume check for sufficient staked amount is done in parent contract + _claimMultiRewards(_aToken); + uint256 stakedAmount = multiUserStaked[msg.sender][_aToken]; + require(stakedAmount >= _amount, "insufficient staked amount"); + multiUserStaked[msg.sender][_aToken] = stakedAmount.sub(_amount); + multiTotalStaked[_aToken] = multiTotalStaked[_aToken].sub(_amount); + + emit multiUnstakeRecorded(msg.sender, _aToken, _amount); + } + + // Harvest rewards + function _claimMultiRewards(address _aToken) internal { + updateMultiRewardAccounting(_aToken); + address[] memory rewardTokenList = multiRewardTokens[_aToken]; + uint256 rewardTokenCount = rewardTokenList.length; + for (uint256 i = 0; i < rewardTokenCount; i++) { + address rewardToken = rewardTokenList[i]; + uint256 earnedAmountScaled = (multiRewardPerToken[_aToken][rewardToken] - + multiUserRewardOffset[msg.sender][_aToken][ + rewardToken + ]) * multiUserStaked[msg.sender][_aToken]; + uint256 earnedAmountWithoutPending = earnedAmountScaled.div(SCALE); + uint256 earnedAmountActual = earnedAmountWithoutPending + multiUserPendingRewards[msg.sender][rewardToken]; + if (earnedAmountActual == 0) { + continue; + } + multiUserRewardOffset[msg.sender][_aToken][ + rewardToken + ] = multiRewardPerToken[_aToken][rewardToken]; + + emit multiRewardHarvested(msg.sender, _aToken, rewardToken, earnedAmountActual); + } + } + + // ╒═════════════════════════✰° + // INTERNAL ACCOUNTING + // °✰════════════════════════╛ + + // Update reward accounting + function updateMultiRewardAccounting(address _aToken) internal { + if(multiTotalStaked[_aToken] == 0) { + return; + } + address[] memory rewardTokenList = multiRewardTokens[_aToken]; + address rewardToken; + uint256 rewardTokenCount = rewardTokenList.length; + for (uint256 i = 0; i < rewardTokenCount; i++) { + rewardToken = rewardTokenList[i]; + uint256 timeElapsed = block.timestamp - + lastMultiUpdateTime[_aToken][rewardToken]; + uint256 rewardToAdd = timeElapsed.mul( + multiRewardPerSecond[_aToken][rewardToken]); + multiRewardPerToken[_aToken][rewardToken] += rewardToAdd + .mul(SCALE) + .div(multiTotalStaked[_aToken]); + + lastMultiUpdateTime[_aToken][rewardToken] = block.timestamp; + } + } + + function calculateUserPending(address _user, address _aToken) internal { + (address[] memory rewardTokenList, uint256[] memory earnedAmounts) = previewEarned( + _user, + _aToken + ); + for(uint256 i = 0; i < rewardTokenList.length; i++) { + address rewardToken = rewardTokenList[i]; + multiUserPendingRewards[_user][rewardToken] += earnedAmounts[i]; + } + + } + + // ╒═════════════════════════✰° + // ADMIN FUNCTIONS + // °✰════════════════════════╛ + + // Add a new reward to a specific aToken + function addIncentiveReward( + address _aToken, + address _rewardToken, + uint256 _rewardsPerSecond + ) external onlyIncentiveAdmin { + address[] memory rewardTokenList = multiRewardTokens[_aToken]; + // check if reward token already exists + for (uint256 i = 0; i < rewardTokenList.length; i++) { + require( + rewardTokenList[i] != _rewardToken, + "reward token already exists" + ); + } + + multiRewardTokens[_aToken].push(_rewardToken); + multiRewardPerSecond[_aToken][_rewardToken] = _rewardsPerSecond; + lastMultiUpdateTime[_aToken][_rewardToken] = block.timestamp; + isMultiRewardToken[_rewardToken] = true; + + emit multiRewardAdded(_aToken, _rewardToken, _rewardsPerSecond); + } + + // Remove a reward from a specific aToken + function removeIncentiveReward( + address _aToken, + address _rewardToken + ) external onlyIncentiveAdmin { + updateMultiRewardAccounting(_aToken); + require(multiRewardPerSecond[_aToken][_rewardToken] != 0, "reward token does not exist"); + + // Stop accumulating new rewards + multiRewardPerSecond[_aToken][_rewardToken] = 0; + + emit multiRewardRemoved(_aToken, _rewardToken); + } + + + // Update reward per second for a specific aToken + function adjustRewardRate(address _aToken, address _rewardToken, uint256 _rewardsPerSecond) external onlyIncentiveAdmin { + require(multiRewardPerSecond[_aToken][_rewardToken] != 0, "reward token does not exist"); + updateMultiRewardAccounting(_aToken); + multiRewardPerSecond[_aToken][_rewardToken] = _rewardsPerSecond; + emit multiRewardUpdated(_aToken, _rewardToken, _rewardsPerSecond); + } + + function depositReward(address _rewardAddress, uint256 _amount) external onlyIncentiveAdmin { + IERC20(_rewardAddress).transferFrom(msg.sender, address(this), _amount); + emit multiRewardDeposited(_rewardAddress, _amount); + } + + function withdrawReward(address _rewardAddress, uint256 _amount) external onlyIncentiveAdmin { + IERC20(_rewardAddress).transfer(msg.sender, _amount); + emit multiRewardWithdrawn(_rewardAddress, _amount); + } + + // ╒═════════════════════════✰° + // USER PREVIEW REWARDS + // °✰════════════════════════╛ + + function previewEarned(address _user, address _aToken) + public + view + returns (address[] memory multiRewardTokens_, uint256[] memory earnedAmounts_) + { + address[] memory rewardTokenList = multiRewardTokens[_aToken]; + uint256 rewardTokenCount = rewardTokenList.length; + multiRewardTokens_ = new address[](rewardTokenCount); + earnedAmounts_ = new uint256[](rewardTokenCount); + uint256 totalStakedAmount = multiTotalStaked[_aToken]; + uint256 userStakedAmount = multiUserStaked[_user][_aToken]; + + if (userStakedAmount == 0 || totalStakedAmount == 0) { + // No staked amount or no total staked amount, earned is zero + return (multiRewardTokens_, earnedAmounts_); + } + + for (uint256 i = 0; i < rewardTokenCount; i++) { + address rewardToken = rewardTokenList[i]; + uint256 simulatedRewardPerToken = _simulateRewardPerToken( + _aToken, + rewardToken, + totalStakedAmount + ); + uint256 earnedAmountActual = _calculateEarnedAmount( + _user, + _aToken, + rewardToken, + simulatedRewardPerToken, + userStakedAmount + ); + earnedAmounts_[i] = earnedAmountActual + multiUserPendingRewards[_user][rewardToken]; + multiRewardTokens_[i] = rewardToken; + } + } + + function _simulateRewardPerToken( + address _aToken, + address _rewardToken, + uint256 totalStakedAmount + ) internal view returns (uint256) { + if (totalStakedAmount == 0) { + return multiRewardPerToken[_aToken][_rewardToken]; + } + + uint256 timeElapsed = block.timestamp - lastMultiUpdateTime[_aToken][_rewardToken]; + uint256 rewardToAdd = timeElapsed.mul(multiRewardPerSecond[_aToken][_rewardToken]); + uint256 simulatedRewardPerToken = multiRewardPerToken[_aToken][_rewardToken].add( + rewardToAdd.mul(SCALE).div(totalStakedAmount) + ); + return simulatedRewardPerToken; + } + + function _calculateEarnedAmount( + address _user, + address _aToken, + address _rewardToken, + uint256 simulatedRewardPerToken, + uint256 userStakedAmount + ) internal view returns (uint256) { + uint256 userRewardPerTokenOffset = multiUserRewardOffset[_user][_aToken][_rewardToken]; + uint256 earnedAmountScaled = simulatedRewardPerToken.sub(userRewardPerTokenOffset) + .mul(userStakedAmount); + uint256 earnedAmountActual = earnedAmountScaled.div(SCALE); + return earnedAmountActual; + } + + + +} \ No newline at end of file diff --git a/src/MasterChef/README.md b/src/MasterChef/README.md new file mode 100644 index 0000000..d9e86fb --- /dev/null +++ b/src/MasterChef/README.md @@ -0,0 +1,138 @@ +# ChefIncentivesMultiRewarder + +The `MasterChefMultiRewarder` contract extends the functionality of the `MasterChef` by enabling multi-token reward distribution for users holding aTokens or liquidity provider (LP) tokens. This enhancement allows for more flexible and diverse incentive mechanisms within decentralized finance (DeFi) protocols. + +--- + +## Overview + +The `MasterChefMultiRewarder` contract introduces the capability to distribute multiple reward tokens to users based on their staked aTokens or LP tokens. It maintains accounting for each reward token separately, ensuring accurate distribution according to predefined rates. + +--- + +## Key Features + +- **Multi-Token Rewards**: Supports distribution of various reward tokens for staked assets. +- **Flexible Reward Rates**: Allows setting and adjusting reward rates per token. +- **Accurate Accounting**: Maintains precise tracking of user stakes and pending rewards. +- **Administrative Control**: Provides functions for administrators to manage reward tokens and rates. + +--- + +## Integration Guide + +To integrate the `MaterChefMultiRewarder` into the `MasterChef`, follow these steps: + +1. **Inherit the Contract**: Modify the `MasterChef` to inherit from `MasterChefMultiRewarder`. + + ```solidity + contract MasterChef is MasterChefMultiRewarder { + // Existing code... + } + ``` + +2. **Modify `deposit` Function**: Update the `deposit` function to incorporate multi-incentive staking and unstaking logic. + + ```solidity + function deposit(address _token, uint256 _amount) external { + // Existing code... + + // Handle multi-reward staking + if (_amount > 0) { + IERC20(_token).safeTransferFrom(msg.sender, address(this), _amount); + stakeIncentiveTokens(_token, _amount); // Multi-reward staking + userAmount = userAmount.add(_amount); + } + + // Existing code... + } + ``` + +3. **Modify `withdraw` Function**: Update the `withdraw` function to include multi-incentive unstaking logic. + + ```solidity + function withdraw(address _token, uint256 _amount) external { + // Existing code... + + // Handle multi-reward unstaking + if (_amount > 0) { + _unstakeIncentiveTokens(_token, _amount); // Multi-reward unstaking + userAmount = userAmount.sub(_amount); + IERC20(_token).safeTransfer(msg.sender, _amount); + } + + } + ``` + +4. **Modify `claim` Function**: Enhance the `claim` function to process multi-token rewards. + + ```solidity + function claim(address _user, address[] calldata _tokens) external { + // Existing code... + + for (uint i = 0; i < _tokens.length; i++) { + PoolInfo storage pool = poolInfo[_tokens[i]]; + require(pool.lastRewardTime > 0, "Pool not initialized"); + _updatePool(_tokens[i], _totalAllocPoint); + + // Claim rewards from MasterChef + UserInfo storage user = userInfo[_tokens[i]][_user]; + uint256 rewardDebt = user.amount.mul(pool.accRewardPerShare).div(1e12); + pending = pending.add(rewardDebt.sub(user.rewardDebt)); + user.rewardDebt = rewardDebt; + + // Claim multi-rewards + _claimMultiRewards(_tokens[i]); // Multi-reward harvesting + } + + // Existing code... + } + ``` + +--- + +## Administrative Functions + +The contract provides several administrative functions for managing rewards: + +- **`addIncentiveReward`**: Adds a new reward token for a specific aToken with a defined reward rate. +- **`removeIncentiveReward`**: Removes an existing reward token from a specific aToken. +- **`adjustRewardRate`**: Updates the reward rate for a specific reward token and aToken pair. +- **`depositReward`**: Deposits reward tokens into the contract for distribution. +- **`withdrawReward`**: Withdraws reward tokens from the contract. + +--- + +## User Functions + +Users can interact with the contract through the following functions: + +- **`_stakeIncentiveTokens`**: Internal function to record staking of aTokens. +- **`_unstakeIncentiveTokens`**: Internal function to record unstaking of aTokens. +- **`_claimMultiRewards`**: Internal function to claim pending multi-token rewards. +- **`previewEarned`**: Public function to view pending rewards for a user and aToken. + +--- + +## Events + +The contract emits several events to facilitate tracking and integration: + +- **`multiStakeRecorded`**: Emitted when a user stakes aTokens. +- **`multiUnstakeRecorded`**: Emitted when a user unstakes aTokens. +- **`multiRewardHarvested`**: Emitted when a user claims rewards. +- **`multiRewardAdded`**: Emitted when a new reward token is added. +- **`multiRewardRemoved`**: Emitted when a reward token is removed. +- **`multiRewardUpdated`**: Emitted when a reward rate is updated. +- **`multiRewardDeposited`**: Emitted when reward tokens are deposited. +- **`multiRewardWithdrawn`**: Emitted when reward tokens are withdrawn. + +--- + +## Disclaimer + +**Important**: This contract has not undergone formal security audits. It is provided "as-is" without any warranties or guarantees. The author assumes no responsibility for any issues, losses, or damages arising from the use of this code. Users are advised to conduct their own thorough testing and audits before deploying this contract in a production environment. + +--- + +By integrating the `ChefIncentivesMultiRewarder`, DeFi protocols can offer more versatile and attractive incentive structures, potentially enhancing user engagement and liquidity provision. \ No newline at end of file diff --git a/src/Standalone/README.md b/src/Standalone/README.md index 8fa1cbe..d97b7b6 100644 --- a/src/Standalone/README.md +++ b/src/Standalone/README.md @@ -57,21 +57,19 @@ The `StandaloneMultiRewarder` contract is a flexible reward distribution module ### User Functions -#### `stakeIncentiveTokens(address _aToken, uint256 _amount, address _user)` +#### `stakeIncentiveTokens(address _aToken, uint256 _amount)` Stake tokens to start earning rewards. - **Parameters**: - `_aToken`: The token address to be staked. - `_amount`: Amount of tokens to stake. - - `_user`: Address of the user staking tokens. -#### `_unstakeIncentiveTokens(address _aToken, uint256 _amount, address _user)` +#### `_unstakeIncentiveTokens(address _aToken, uint256 _amount)` Unstake tokens and claim rewards. - **Parameters**: - `_aToken`: The token address to be unstaked. - `_amount`: Amount of tokens to unstake. - - `_user`: Address of the user unstaking tokens. #### `previewEarned(address _user, address _aToken)` View pending rewards for a specific user and staked token. diff --git a/src/Standalone/StandaloneMultiRewarder.sol b/src/Standalone/StandaloneMultiRewarder.sol index 60a9ac5..b51cd84 100644 --- a/src/Standalone/StandaloneMultiRewarder.sol +++ b/src/Standalone/StandaloneMultiRewarder.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.7.6; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; -import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; +import { SafeMath} from "../dependencies/openzeppelin/contracts/SafeMath.sol"; +import { IERC20 } from "../dependencies/openzeppelin/contracts/IERC20.sol"; +import { SafeERC20 } from "../dependencies/openzeppelin/contracts/SafeERC20.sol"; contract StandaloneMultiRewarder { @@ -69,14 +69,14 @@ contract StandaloneMultiRewarder { // °✰════════════════════╛ // Stake aToken - function stakeIncentiveTokens(address _aToken, uint256 _amount, address _user) external { + function stakeIncentiveTokens(address _aToken, uint256 _amount) external { require(_amount > 0, "amount must be greater than 0"); - calculateUserPending(_user, _aToken); + calculateUserPending(msg.sender, _aToken); // Update rewards before modifying stakes to ensure proper accounting updateMultiRewardAccounting(_aToken); multiTotalStaked[_aToken] = multiTotalStaked[_aToken].add(_amount); - multiUserStaked[_user][_aToken] = multiUserStaked[_user][_aToken].add(_amount); + multiUserStaked[msg.sender][_aToken] = multiUserStaked[msg.sender][_aToken].add(_amount); address[] memory rewardTokenList = multiRewardTokens[_aToken]; uint256 rewardTokenCount = rewardTokenList.length; @@ -84,53 +84,53 @@ contract StandaloneMultiRewarder { for (uint256 i = 0; i < rewardTokenCount; i++) { address rewardToken = rewardTokenList[i]; // Set the offset to current rewardPerToken to start counting from this point - multiUserRewardOffset[_user][_aToken][rewardToken] = + multiUserRewardOffset[msg.sender][_aToken][rewardToken] = _simulateRewardPerToken(_aToken, rewardToken, multiTotalStaked[_aToken].sub(_amount)); } - SafeERC20.safeTransferFrom(IERC20(_aToken), _user, address(this), _amount); + SafeERC20.safeTransferFrom(IERC20(_aToken), msg.sender, address(this), _amount); - emit multiStakeRecorded(_user, _aToken, _amount); + emit multiStakeRecorded(msg.sender, _aToken, _amount); } // Withdraw aToken - function _unstakeIncentiveTokens(address _aToken, uint256 _amount, address _user) external { + function unstakeIncentiveTokens(address _aToken, uint256 _amount) external { require(_amount > 0, "amount must be greater than 0"); - require(_amount <= multiUserStaked[_user][_aToken], "insufficient staked amount"); + require(_amount <= multiUserStaked[msg.sender][_aToken], "insufficient staked amount"); - _claimMultiRewards(_aToken, _user); - uint256 stakedAmount = multiUserStaked[_user][_aToken]; + _claimMultiRewards(_aToken, msg.sender); + uint256 stakedAmount = multiUserStaked[msg.sender][_aToken]; require(stakedAmount >= _amount, "insufficient staked amount"); - multiUserStaked[_user][_aToken] = stakedAmount.sub(_amount); + multiUserStaked[msg.sender][_aToken] = stakedAmount.sub(_amount); multiTotalStaked[_aToken] = multiTotalStaked[_aToken].sub(_amount); - SafeERC20.safeTransfer(IERC20(_aToken), _user, _amount); + SafeERC20.safeTransfer(IERC20(_aToken), msg.sender, _amount); - emit multiUnstakeRecorded(_user, _aToken, _amount); + emit multiUnstakeRecorded(msg.sender, _aToken, _amount); } // Harvest rewards - function _claimMultiRewards(address _aToken, address _user) internal { + function claimMultiRewards(address _aToken) public { updateMultiRewardAccounting(_aToken); address[] memory rewardTokenList = multiRewardTokens[_aToken]; uint256 rewardTokenCount = rewardTokenList.length; for (uint256 i = 0; i < rewardTokenCount; i++) { address rewardToken = rewardTokenList[i]; uint256 earnedAmountScaled = (multiRewardPerToken[_aToken][rewardToken] - - multiUserRewardOffset[_user][_aToken][ + multiUserRewardOffset[msg.sender][_aToken][ rewardToken - ]) * multiUserStaked[_user][_aToken]; + ]) * multiUserStaked[msg.sender][_aToken]; uint256 earnedAmountWithoutPending = earnedAmountScaled.div(SCALE); - uint256 earnedAmountActual = earnedAmountWithoutPending + multiUserPendingRewards[_user][rewardToken]; + uint256 earnedAmountActual = earnedAmountWithoutPending + multiUserPendingRewards[msg.sender][rewardToken]; if (earnedAmountActual == 0) { continue; } - multiUserRewardOffset[_user][_aToken][ + multiUserRewardOffset[msg.sender][_aToken][ rewardToken ] = multiRewardPerToken[_aToken][rewardToken]; - SafeERC20.safeTransfer(IERC20(rewardToken), _user, earnedAmountActual); - - emit multiRewardHarvested(_user, _aToken, rewardToken, earnedAmountActual); + SafeERC20.safeTransfer(IERC20(rewardToken), msg.sender, earnedAmountActual); + + emit multiRewardHarvested(msg.sender, _aToken, rewardToken, earnedAmountActual); } } diff --git a/src/dependencies/openzeppelin/contracts/IERC20.sol b/src/dependencies/openzeppelin/contracts/IERC20.sol new file mode 100644 index 0000000..3d53292 --- /dev/null +++ b/src/dependencies/openzeppelin/contracts/IERC20.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/IERC20.sol) + +pragma solidity 0.7.6; + +/** + * @dev Interface of the ERC-20 standard as defined in the ERC. + */ +interface IERC20 { + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); + + /** + * @dev Returns the value of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the value of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves a `value` amount of tokens from the caller's account to `to`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address to, uint256 value) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets a `value` amount of tokens as the allowance of `spender` over the + * caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 value) external returns (bool); + + /** + * @dev Moves a `value` amount of tokens from `from` to `to` using the + * allowance mechanism. `value` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom(address from, address to, uint256 value) external returns (bool); +} \ No newline at end of file diff --git a/src/dependencies/openzeppelin/contracts/SafeERC20.sol b/src/dependencies/openzeppelin/contracts/SafeERC20.sol new file mode 100644 index 0000000..e62d720 --- /dev/null +++ b/src/dependencies/openzeppelin/contracts/SafeERC20.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/utils/SafeERC20.sol) + +pragma solidity ^0.8.20; + +import {IERC20} from "./IERC20.sol"; +import {IERC1363} from "../../../interfaces/IERC1363.sol"; + +/** + * @title SafeERC20 + * @dev Wrappers around ERC-20 operations that throw on failure (when the token + * contract returns false). Tokens that return no value (and instead revert or + * throw on failure) are also supported, non-reverting calls are assumed to be + * successful. + * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, + * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + */ +library SafeERC20 { + /** + * @dev An operation with an ERC-20 token failed. + */ + error SafeERC20FailedOperation(address token); + + /** + * @dev Indicates a failed `decreaseAllowance` request. + */ + error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease); + + /** + * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + */ + function safeTransfer(IERC20 token, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value))); + } + + /** + * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the + * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + */ + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value))); + } + + /** + * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. + */ + function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { + uint256 oldAllowance = token.allowance(address(this), spender); + forceApprove(token, spender, oldAllowance + value); + } + + /** + * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no + * value, non-reverting calls are assumed to be successful. + * + * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" + * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using + * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract + * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. + */ + 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); + } + } + + /** + * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, + * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval + * to be set to zero before setting it to a non-zero value, such as USDT. + * + * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function + * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being + * set here. + */ + 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); + } + } + + /** + * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no + * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when + * targeting contracts. + * + * Reverts if the returned value is other than `true`. + */ + function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { + if (to.code.length == 0) { + safeTransfer(token, to, value); + } else if (!token.transferAndCall(to, value, data)) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom if the target + * has no code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when + * targeting contracts. + * + * Reverts if the returned value is other than `true`. + */ + function transferFromAndCallRelaxed( + IERC1363 token, + address from, + address to, + uint256 value, + bytes memory data + ) internal { + if (to.code.length == 0) { + safeTransferFrom(token, from, to, value); + } else if (!token.transferFromAndCall(from, to, value, data)) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve if the target has no + * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when + * targeting contracts. + * + * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}. + * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall} + * once without retrying, and relies on the returned value to be true. + * + * Reverts if the returned value is other than `true`. + */ + function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal { + if (to.code.length == 0) { + forceApprove(token, to, value); + } else if (!token.approveAndCall(to, value, data)) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements. + */ + function _callOptionalReturn(IERC20 token, bytes memory data) private { + uint256 returnSize; + uint256 returnValue; + assembly ("memory-safe") { + let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) + // bubble errors + if iszero(success) { + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + revert(ptr, returndatasize()) + } + returnSize := returndatasize() + returnValue := mload(0) + } + + if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) { + revert SafeERC20FailedOperation(address(token)); + } + } + + /** + * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement + * on the return value: the return value is optional (but if data is returned, it must not be false). + * @param token The token targeted by the call. + * @param data The call data (encoded using abi.encode or one of its variants). + * + * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead. + */ + function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { + bool success; + uint256 returnSize; + uint256 returnValue; + assembly ("memory-safe") { + success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20) + returnSize := returndatasize() + returnValue := mload(0) + } + return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1); + } +} \ No newline at end of file diff --git a/src/dependencies/openzeppelin/contracts/SafeMath.sol b/src/dependencies/openzeppelin/contracts/SafeMath.sol new file mode 100644 index 0000000..309d17f --- /dev/null +++ b/src/dependencies/openzeppelin/contracts/SafeMath.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + + +/** + * @title SafeMath + * @dev Math operations with safety checks that throw on error + */ +library SafeMath { + + /** + * @dev Multiplies two numbers, throws on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256 c) { + if (a == 0) { + return 0; + } + c = a * b; + assert(c / a == b); + return c; + } + + /** + * @dev Integer division of two numbers, truncating the quotient. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + // assert(b > 0); // Solidity automatically throws when dividing by 0 + // uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + return a / b; + } + + /** + * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + assert(b <= a); + return a - b; + } + + /** + * @dev Adds two numbers, throws on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256 c) { + c = a + b; + assert(c >= a); + return c; + } +} \ No newline at end of file