// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract RewardToken is ERC20, Ownable {
// Mapping to store contracts that are allowed to mint
mapping(address => bool) public minters;
constructor(string memory name, string memory symbol) ERC20(name, symbol) Ownable(msg.sender) {
minters[msg.sender] = true;
}
function mint(address _to, uint256 _amount) external {
require(minters[msg.sender], "Caller is not authorized to mint");
_mint(_to, _amount);
emit TokensMinted(_to, _amount);
}
event TokensMinted(address indexed to, uint256 amount);
}
@openzeppelin/contracts/access/Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
@openzeppelin/contracts/token/ERC20/ERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
@openzeppelin/contracts/token/ERC20/IERC20.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
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);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/IERC721Receiver.sol)
pragma solidity ^0.8.20;
/**
* @title ERC721 token receiver interface
* @dev Interface for any contract that wants to support safeTransfers
* from ERC721 asset contracts.
*/
interface IERC721Receiver {
/**
* @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
* by `operator` from `from`, this function is called.
*
* It must return its Solidity selector to confirm the token transfer.
* If any other value is returned or the interface is not implemented by the recipient, the transfer will be
* reverted.
*
* The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
*/
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
@openzeppelin/contracts/utils/Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
@openzeppelin/contracts/utils/ReentrancyGuard.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
contracts/YieldFarm.sol
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces.sol";
import "./RewardToken.sol";
contract YieldFarm is Ownable, ReentrancyGuard, IERC721Receiver {
struct Farm {
uint256 id; // Farm ID
uint8 version; // V2 or V3
string name; // Descriptor of the farm
address poolAddr; // Address of V2 pair or V3 pool
uint256 liquidity; // Total liquidity deposited
uint256 allocPoint; // Allocation points assigned to the pool
uint256 lastCalcBlock; // Last block number the user had their rewards calculated
uint256 accRewardsPerShare; // Accumulated rewards per share times CALC_PRECISION
uint256 accThirdPartyRewardsPerShare; // Accumulated third party rewards per share times CALC_PRECISION
address[] farmers; // Array of all farmer addresses in the farm
uint256 farmerCount; // Array of all farmer addresses in the farm
address token0; // Address of the 1st token in the pair
address token1; // Address of the 2nd token in the pair
// Only used for V3
uint256 tokenId;
int24 tickLower;
int24 tickUpper;
uint24 fee;
uint256 accFees0PerShare; // Accumulated fees0 per share times CALC_PRECISION
uint256 accFees1PerShare; // Accumulated fees1 per share times CALC_PRECISION
bool active;
}
struct Farmer {
address addr;
uint256 liquidity; // How much liquidity the farmer has provided
uint256 boltMultiplier; // Multiplier for deposited/staked BOLT, stored as percentage with two decimals
uint256 boltDeposited; // Amount of BOLT depositted
uint256 durationMultiplier; // Transient, populated by getFarmerByFarmIdAndAddress
uint256 startingBlock; // Used in calculation of duration bonus multiplier
uint256 rewards; // Transient
uint256 rewardDebt; // The amount relative to farm.accRewardsPerShare the user can't get as reward
uint256 thirdPartyRewards; // Transient
uint256 thirdPartyRewardDebt; // The amount relative to farm.accThirdPartyRewardsPerShare the user can't get as reward
// Only used for V3
uint256 fees0; // Transient
uint256 fees0Debt; // The amount relative to farm.accFees0PerShare the user can't claim
uint256 fees1; // Transient
uint256 fees1Debt; // The amount relative to farm.accFees1PerShare the user can't claim
}
struct ThirdPartyReward {
address token;
address tokenManager;
uint256 tokensPerBlock;
uint256 endBlock;
}
RewardToken public rewardToken; // Token to be minted as farming reward
IElectroSwapV2Router internal v2Router; // For adding and removing V2 liquidity
IElectroSwapV3Factory internal v3Factory; // Used to get V3 pool addresses
INonfungiblePositionManager internal positionManager; // Used to get position info and add/remove liquidity
IUncollectedFeeHelper internal feeHelper;
address internal wetnAddress;
address internal v2Factory; // Used to verify ElectroSwap V2 LP tokens
address internal boltToken; // BOLT Token address
uint256 public totalAllocPoint; // Tracks total allocation points across all pools
uint256 public rewardPerBlock; // Number of rewards per block shared across all incentivized pools
uint256 internal constant CALC_PRECISION = 1e18; // A big number to perform mul and div operations
uint256 internal constant MAX_BLOCKS_FOR_BONUS = 6307200; // 1 year worth of blocks
address internal constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
// Tracks state of current farms and farmers
uint256 public farmCount;
mapping(address => uint256[]) internal poolFarmIds; // mapping(address => farmId[])
mapping(uint256 => Farm) internal farmInfo; // mapping(farmId => Farm))
mapping(uint256 => mapping(address => Farmer)) internal farmerInfo; // mapping(farmId => mapping(farmerAddr => Farmer))
mapping(uint256 => ThirdPartyReward) internal thirdPartyRewards; // mapping(farmId => ThirdPartyReward))
mapping(uint256 => uint256) internal boltMultipliers; // mapping(valid BOLT deposit => bonus multiplier padded by 10000X)
constructor(
string memory _rewardName, string memory _rewardSymbol, uint256 _rewardSupply, uint256 _rewardPerBlock, // Reward related params
address _wetnAddress, address _boltToken, // WETN and BOLT address
address _v2Factory, address _v2Router, // V2 params
address _v3Factory, address _positionManager, address _feeHelper // V3 params
) Ownable(msg.sender) {
rewardToken = new RewardToken(_rewardName, _rewardSymbol);
rewardToken.mint(msg.sender, _rewardSupply);
rewardToken.renounceOwnership();
rewardPerBlock = _rewardPerBlock;
boltToken = _boltToken;
v2Factory = _v2Factory;
v2Router = IElectroSwapV2Router(_v2Router);
positionManager = INonfungiblePositionManager(_positionManager);
v3Factory = IElectroSwapV3Factory(_v3Factory);
feeHelper = IUncollectedFeeHelper(_feeHelper);
wetnAddress = _wetnAddress;
boltMultipliers[0] = 10000; // 1.00X Reward multiplier (not changeable)
boltMultipliers[50000 * 1e18] = 10500; // 1.05X Reward multiplier
boltMultipliers[100000 * 1e18] = 11500; // 1.15X Reward multiplier
}
/** ==================== ADMIN FUNCTIONS ==================== */
// Add a new incentivized V2 farm by passing an existing V2 pair address. Can only be called by the owner (ElectroSwap).
function adminCreateFarm_V2(string memory _name, address _pairAddr, uint256 _allocPoint) external onlyOwner nonReentrant {
require(_pairAddr != address(0), "Cannot add zero address");
require(poolFarmIds[_pairAddr].length == 0, "Pool already added");
require(IElectroSwapV2Pair(_pairAddr).factory() == v2Factory, "Invalid LP Token");
_updateAllFarmRewardsAndFees();
totalAllocPoint += _allocPoint;
IElectroSwapV2Pair pair = IElectroSwapV2Pair(_pairAddr);
address token0 = pair.token0();
address token1 = pair.token1();
farmCount++;
poolFarmIds[_pairAddr].push(farmCount);
address[] memory farmerAddresses; // empty array
farmInfo[farmCount] = Farm({
id: farmCount,
version: 2,
name: _name,
poolAddr: _pairAddr,
liquidity: 0,
allocPoint: _allocPoint,
lastCalcBlock: block.number,
accRewardsPerShare: 0,
accThirdPartyRewardsPerShare: 0,
farmers: farmerAddresses,
farmerCount: 0,
token0: token0,
token1: token1,
tokenId: 0,
tickLower: 0,
tickUpper: 0,
fee: 0,
accFees0PerShare: 0,
accFees1PerShare: 0,
active: true
});
emit FarmCreated(farmCount, _pairAddr, 2, 0, _allocPoint, totalAllocPoint);
}
// Add a new incentivized V3 farm by passing a position token ID, which requires prior approval. Can only be called by the owner (ElectroSwap).
function adminCreateFarm_V3(string memory _name, uint256 _tokenId, uint256 _allocPoint) external onlyOwner nonReentrant {
Position memory position = _getPosition(_tokenId);
require(position.token0 != address(0) && position.token1 != address(0), "Invalid tokenId");
positionManager.safeTransferFrom(msg.sender, address(this), _tokenId);
address poolAddr = v3Factory.getPool(position.token0, position.token1, position.fee);
_updateAllFarmRewardsAndFees();
totalAllocPoint += _allocPoint;
farmCount++;
poolFarmIds[poolAddr].push(farmCount);
address[] memory farmerAddresses = new address[](1);
farmerAddresses[0] = msg.sender;
farmInfo[farmCount] = Farm({
id: farmCount,
version: 3,
name: _name,
poolAddr: poolAddr,
liquidity: position.liquidity,
allocPoint: _allocPoint,
lastCalcBlock: block.number,
accRewardsPerShare: 0,
accThirdPartyRewardsPerShare: 0,
farmers: farmerAddresses,
farmerCount: 1,
tokenId: _tokenId,
token0: position.token0,
token1: position.token1,
tickLower: position.tickLower,
tickUpper: position.tickUpper,
fee: position.fee,
accFees0PerShare: 0,
accFees1PerShare: 0,
active: true
});
farmerInfo[farmCount][msg.sender] = Farmer({
addr: msg.sender,
liquidity: position.liquidity,
boltMultiplier: boltMultipliers[0], // 1.00X
boltDeposited: 0,
durationMultiplier: 0,
startingBlock: block.number,
rewards: 0,
rewardDebt: 0,
thirdPartyRewards: 0,
thirdPartyRewardDebt: 0,
fees0: 0,
fees0Debt: 0,
fees1: 0,
fees1Debt: 0
});
emit FarmCreated(farmCount, poolAddr, 3, _tokenId, _allocPoint, totalAllocPoint);
}
// Update the farm's allocation points. Can only be called by the owner (ElectroSwap).
function adminUpdateFarmAllocPoints(uint256 _farmId, uint256 _allocPoint) external onlyOwner nonReentrant {
Farm storage farm = farmInfo[_farmId];
require(farm.poolAddr != address(0), "Invalid pool");
_updateAllFarmRewardsAndFees();
totalAllocPoint = totalAllocPoint - farm.allocPoint + _allocPoint;
farm.allocPoint = _allocPoint;
emit FarmAllocationUpdated(_farmId, farm.poolAddr, _allocPoint, totalAllocPoint);
}
// Update the farm's name. Can only be called by the owner (ElectroSwap).
function adminUpdateFarmName(uint256 _farmId, string memory _name) external onlyOwner nonReentrant {
Farm storage farm = farmInfo[_farmId];
require(farm.poolAddr != address(0), "Invalid pool");
farm.name = _name;
emit FarmNameUpdated(_farmId, farm.poolAddr, _name);
}
// Update the number of rewards per block. Can only be called by the owner (ElectroSwap).
function adminUpdateRewardsPerBlock(uint256 _rewardPerBlock) external onlyOwner nonReentrant {
uint256 prevRewardPerBlock = rewardPerBlock;
rewardPerBlock = _rewardPerBlock;
_updateAllFarmRewardsAndFees();
emit RewardsPerBlockUpdated(rewardPerBlock, prevRewardPerBlock);
}
// Update the valid BOLT deposit amounts and corresponding multipliers. Can only be called by the owner (ElectroSwap).
function adminUpdateBoltMultiplier(uint256 _amountBolt, uint256 _multiplier) external onlyOwner nonReentrant {
require(_multiplier >= 10000, "Multiplier must be >= 10000");
require(_amountBolt > 0, "Amount must be > 0");
boltMultipliers[_amountBolt] = _multiplier;
emit BoltMultiplierUpdated(_amountBolt, _multiplier);
}
// Remove an incentivized farm. Still allows withdrawls, but no deposits. Can only be called by the owner (ElectroSwap).
function adminDeactivateFarm(uint256 _farmId) external onlyOwner nonReentrant {
Farm storage farm = farmInfo[_farmId];
require(farm.poolAddr != address(0), "Nothing to deactivate");
_updateAllFarmRewardsAndFees();
totalAllocPoint -= farm.allocPoint;
emit FarmDeactivated(_farmId, farm.poolAddr, farm.version, farm.tokenId);
farm.active = false;
farm.allocPoint = 0;
}
// Add third party rewards to a farm. Can only be called by the owner (ElectroSwap).
function adminAddThirdPartyReward(uint256 _farmId, address _token, address _tokenManager) external onlyOwner nonReentrant {
require(_token != address(0) && _tokenManager != address(0), "Cannot be zero address");
thirdPartyRewards[_farmId] = ThirdPartyReward({
token: _token,
tokenManager: _tokenManager, // Address of user that can update the tokensPerBlock
tokensPerBlock: 0,
endBlock: 0
});
emit ThirdPartyRewardAdded(_farmId, _token, _tokenManager);
}
/** ==================== PRIMARY FUNCTIONS ==================== */
// Deposit tokens into yield farm for rewards allocation.
function deposit(uint256 _farmId, uint256 _amount0, uint256 _amount1, uint256 _amountBolt) payable external nonReentrant {
Farm storage farm = farmInfo[_farmId];
require(farm.active, "Inactive farm");
_updateFarmRewardsAndFees(_farmId);
Farmer storage farmer = farmerInfo[_farmId][msg.sender];
bool token0Native = false;
bool token1Native = false;
// Wrap native ETN and transfer other token from msg.sender to the yield farming contract. Requires prior approval
if(msg.value > 0){
require(farm.token0 == wetnAddress || farm.token1 == wetnAddress, "Native ETN sent to unsupporting farm");
IWETN(wetnAddress).deposit{value: msg.value}();
if(farm.token0 == wetnAddress){
_amount0 = msg.value;
token0Native = true;
IERC20(farm.token1).transferFrom(msg.sender, address(this), _amount1);
} else {
_amount1 = msg.value;
token1Native = true;
IERC20(farm.token0).transferFrom(msg.sender, address(this), _amount0);
}
}
// Transfer both tokens from msg.sender to the yield farming contract. Requires prior approval
else {
IERC20(farm.token0).transferFrom(msg.sender, address(this), _amount0);
IERC20(farm.token1).transferFrom(msg.sender, address(this), _amount1);
}
require(_amount0 > 0 && _amount1 > 0, "Both amounts must be positive");
(uint256 liquidityAdded, uint256 amount0Added, uint256 amount1Added) = farm.version == 2 ?
_depositV2(farm, _amount0, _amount1) :
_depositV3(farm, _amount0, _amount1);
// Refund whatever was not added to the position to msg.sender
_refundIfNeeded(farm.token0, token0Native, _amount0, amount0Added);
_refundIfNeeded(farm.token1, token1Native, _amount1, amount1Added);
farm.liquidity += liquidityAdded;
// New farmer
if (farmer.liquidity == 0) {
farm.farmers.push(msg.sender); // Add farmer to the pool's farmer list
farm.farmerCount += 1;
farmer.addr = msg.sender;
farmer.liquidity = liquidityAdded;
farmer.boltMultiplier = boltMultipliers[0]; // Default value
farmer.startingBlock = block.number;
farmer.rewardDebt = liquidityAdded * farm.accRewardsPerShare / CALC_PRECISION;
farmer.thirdPartyRewardDebt = liquidityAdded * farm.accThirdPartyRewardsPerShare / CALC_PRECISION;
farmer.fees0Debt = liquidityAdded * farm.accFees0PerShare / CALC_PRECISION;
farmer.fees1Debt = liquidityAdded * farm.accFees1PerShare / CALC_PRECISION;
emit FarmDeposit(_farmId, msg.sender, amount0Added, amount1Added, liquidityAdded);
}
else {
// Adjust starting block based on amount of liquidity added to prevent unfair duration multiplier manipulation
uint256 increaseOfTotal = (liquidityAdded * 1e18) / (farmer.liquidity + liquidityAdded);
uint256 blocksServed = block.number - farmer.startingBlock;
uint256 adjustedBlocksServed = blocksServed - ((blocksServed * increaseOfTotal) / 1e18); // Resolves to zero if negative
farmer.startingBlock = block.number - adjustedBlocksServed;
uint256 rewardsEarned = (farmer.liquidity * farm.accRewardsPerShare / CALC_PRECISION) - farmer.rewardDebt;
uint256 thirdPartyRewardsEarned = (farmer.liquidity * farm.accThirdPartyRewardsPerShare / CALC_PRECISION) - farmer.thirdPartyRewardDebt;
uint256 fees0Earned = (farmer.liquidity * farm.accFees0PerShare / CALC_PRECISION) - farmer.fees0Debt;
uint256 fees1Earned = (farmer.liquidity * farm.accFees1PerShare / CALC_PRECISION) - farmer.fees1Debt;
farmer.liquidity += liquidityAdded;
farmer.rewardDebt = (farmer.liquidity * farm.accRewardsPerShare / CALC_PRECISION) - rewardsEarned;
farmer.thirdPartyRewardDebt = (farmer.liquidity * farm.accThirdPartyRewardsPerShare / CALC_PRECISION) - thirdPartyRewardsEarned;
farmer.fees0Debt = (farmer.liquidity * farm.accFees0PerShare / CALC_PRECISION) - fees0Earned;
farmer.fees1Debt = (farmer.liquidity * farm.accFees1PerShare / CALC_PRECISION) - fees1Earned;
emit FarmIncrease(_farmId, msg.sender, amount0Added, amount1Added, liquidityAdded, farmer.startingBlock);
}
if(_amountBolt > 0){
uint256 totalBolt = _amountBolt + farmer.boltDeposited;
require(boltMultipliers[totalBolt] != 0, "Invalid BOLT deposit");
IERC20(boltToken).transferFrom(msg.sender, address(this), _amountBolt);
farmer.boltDeposited = totalBolt;
farmer.boltMultiplier = boltMultipliers[totalBolt];
}
}
// Withdraw tokens AND rewards. If _asNative is true AND the pair contains WETN, native ETN will be sent.
function withdraw(uint256 _farmId, uint256 _liquidityAmt, bool _asNative) external nonReentrant {
Farm storage farm = farmInfo[_farmId];
Farmer storage farmer = farmerInfo[_farmId][msg.sender];
require(farmer.liquidity >= _liquidityAmt, "Cannot withdraw more than deposited");
(uint256 rewardsMinted, uint256 fees0Collected, uint256 fees1Collected, uint256 thirdPartyRewardsCollected) = _collectRewardsAndFees(_farmId, _asNative);
farmer.liquidity -= _liquidityAmt;
farmer.rewardDebt = farmer.liquidity * farm.accRewardsPerShare / CALC_PRECISION;
if(farm.version == 3){
farmer.fees0Debt = farmer.liquidity * farm.accFees0PerShare / CALC_PRECISION;
farmer.fees1Debt = farmer.liquidity * farm.accFees1PerShare / CALC_PRECISION;
}
if(thirdPartyRewards[_farmId].token != address(0)){
farmer.thirdPartyRewardDebt = farmer.liquidity * farm.accThirdPartyRewardsPerShare / CALC_PRECISION;
}
// Just withdraw rewards and fees
if(_liquidityAmt == 0){
emit FarmWithdrawl(_farmId, msg.sender, 0, 0, rewardsMinted, fees0Collected, fees1Collected, thirdPartyRewardsCollected);
return;
}
if(_asNative){
require(farm.token0 == wetnAddress || farm.token1 == wetnAddress, "Unsupporting farm");
}
// Includes removed liquidity and fees
(uint256 amount0, uint256 amount1) = farm.version == 2 ? _withdrawV2(farm, _liquidityAmt, _asNative) : _withdrawV3(farm, _liquidityAmt, _asNative);
// Clear state on removal of all liquidity
if (farmer.liquidity == 0) {
// Withdraw all BOLT
if(farmer.boltDeposited > 0){
IERC20(boltToken).transfer(msg.sender, farmer.boltDeposited);
}
delete farmerInfo[_farmId][msg.sender];
_removeFarmer(_farmId, msg.sender);
}
farm.liquidity -= _liquidityAmt;
emit FarmWithdrawl(_farmId, msg.sender, amount0, amount1, rewardsMinted, fees0Collected, fees1Collected, thirdPartyRewardsCollected);
}
/** ==================== INTERNAL FUNCTIONS ==================== */
// Deposits tokens into V2 yield farm for rewards allocation.
function _depositV2(Farm memory _farm, uint256 _amount0, uint256 _amount1) internal returns (uint256 liquidityAdded, uint256 amount0Added, uint256 amount1Added) {
// Approve the router to spend the tokens
IERC20(_farm.token0).approve(address(v2Router), _amount0);
IERC20(_farm.token1).approve(address(v2Router), _amount1);
(amount0Added, amount1Added, liquidityAdded) = v2Router.addLiquidity(
_farm.token0,
_farm.token1,
_amount0,
_amount1,
1,
1,
address(this),
block.timestamp
);
}
// Withdraw tokens AND rewards.
function _withdrawV2(Farm memory _farm, uint256 _liquidityAmt, bool _asNative) internal returns (uint256 amount0, uint256 amount1){
// Transfers token0 and token1 back to this contract
IERC20(_farm.poolAddr).approve(address(v2Router), _liquidityAmt);
(amount0, amount1) = v2Router.removeLiquidity(_farm.token0, _farm.token1, _liquidityAmt, 1, 1, address(this), block.timestamp);
_transferTokens(_farm, amount0, amount1, _asNative);
}
// Deposit tokens into V3 yield farm for rewards allocation.
function _depositV3(Farm memory _farm, uint256 _amount0, uint256 _amount1) internal returns (uint256 liquidityAdded, uint256 amount0Added, uint256 amount1Added) {
// Approve the positionManager to spend the tokens
IERC20(_farm.token0).approve(address(positionManager), _amount0);
IERC20(_farm.token1).approve(address(positionManager), _amount1);
// Add liquidity to the existing position
(liquidityAdded, amount0Added, amount1Added) = positionManager.increaseLiquidity(
INonfungiblePositionManager.IncreaseLiquidityParams({
tokenId: _farm.tokenId,
amount0Desired: _amount0,
amount1Desired: _amount1,
amount0Min: 1,
amount1Min: 1,
deadline: block.timestamp
})
);
}
function _withdrawV3(Farm storage _farm, uint256 _liquidityAmt, bool _asNative) internal returns (uint256 amount0, uint256 amount1) {
// Decreases available liquidity before collecting
(amount0, amount1) = positionManager.decreaseLiquidity(
INonfungiblePositionManager.DecreaseLiquidityParams({
tokenId: _farm.tokenId,
liquidity: uint128(_liquidityAmt),
amount0Min: 1,
amount1Min: 1,
deadline: block.timestamp
})
);
// Transfers token0 and token back to this contract
_collectV3(_farm.tokenId);
_transferTokens(_farm, amount0, amount1, _asNative);
}
function _transferTokens(Farm memory _farm, uint256 amount0, uint256 amount1, bool _asNative) internal {
if(_asNative && _farm.token0 == wetnAddress){
_withdrawNativeETN(amount0);
IERC20(_farm.token1).transfer(msg.sender, amount1);
}
else if(_asNative && _farm.token1 == wetnAddress){
_withdrawNativeETN(amount1);
IERC20(_farm.token0).transfer(msg.sender, amount0);
}
else {
IERC20(_farm.token0).transfer(msg.sender, amount0);
IERC20(_farm.token1).transfer(msg.sender, amount1);
}
}
function _refundIfNeeded(address token, bool tokenIsNative, uint256 amountDeposited, uint256 amountAdded) internal {
if(amountDeposited > amountAdded){
uint256 refundAmount = amountDeposited - amountAdded;
if(tokenIsNative){
_withdrawNativeETN(refundAmount);
} else {
IERC20(token).transfer(msg.sender, refundAmount);
}
}
}
function _withdrawNativeETN(uint256 amount) internal {
IWETN(wetnAddress).withdraw(amount);
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "ETN transfer failed");
}
function _collectV3(uint256 tokenId) internal returns (uint256 amount0Collected, uint256 amount1Collected){
(amount0Collected, amount1Collected) = positionManager.collect(
INonfungiblePositionManager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
}
function _collectRewardsAndFees(uint256 _farmId, bool _asNative) internal returns (uint256 rewardsCollected, uint256 fees0Collected, uint256 fees1Collected, uint256 thirdPartyRewardsCollected) {
_updateFarmRewardsAndFees(_farmId);
Farmer storage farmer = farmerInfo[_farmId][msg.sender];
if(farmer.liquidity > 0){
Farm storage farm = farmInfo[_farmId];
uint256 newRewardDebt = farmer.liquidity * farm.accRewardsPerShare / CALC_PRECISION;
uint256 rewardsEarned = newRewardDebt - farmer.rewardDebt;
// boltMultiplier and durationMultiplier are both multiplied by 10000, so we divide by 100000000
rewardsCollected = (rewardsEarned * farmer.boltMultiplier * _calculateDurationMultiplier(block.number - farmer.startingBlock)) / 100000000;
if (rewardsCollected != 0) {
rewardToken.mint(msg.sender, rewardsCollected);
}
if(farm.version == 3){
uint256 newFee0Debt = farmer.liquidity * farm.accFees0PerShare / CALC_PRECISION;
fees0Collected = newFee0Debt - farmer.fees0Debt;
uint256 newFee1Debt = farmer.liquidity * farm.accFees1PerShare / CALC_PRECISION;
fees1Collected = newFee1Debt - farmer.fees1Debt;
_transferTokens(farm, fees0Collected, fees1Collected, _asNative);
}
ThirdPartyReward memory thirdPartyReward = thirdPartyRewards[_farmId];
if(thirdPartyReward.token != address(0)){
uint256 newThirdPartyDebt = farmer.liquidity * farm.accThirdPartyRewardsPerShare / CALC_PRECISION;
thirdPartyRewardsCollected = newThirdPartyDebt - farmer.thirdPartyRewardDebt;
if (thirdPartyRewardsCollected != 0) {
IERC20(thirdPartyReward.token).transfer(msg.sender, thirdPartyRewardsCollected);
}
}
}
}
// Called prior to modifying totalAllocPoint or rewardPerBlock (by admin)
function _updateAllFarmRewardsAndFees() private {
for (uint256 i = 1; i <= farmCount; i++) {
_updateFarmRewardsAndFees(i);
}
}
// Sets accumulated amounts for rewards, third party rewards and fees by share
function _updateFarmRewardsAndFees(uint256 _farmId) private {
Farm storage farm = farmInfo[_farmId];
if (farm.liquidity == 0 || !farm.active) {
farm.lastCalcBlock = block.number;
return;
}
uint256 blocksSinceLastCalc = block.number - farm.lastCalcBlock;
uint256 rewards = blocksSinceLastCalc * ((rewardPerBlock * farm.allocPoint) / totalAllocPoint);
farm.accRewardsPerShare += rewards * CALC_PRECISION / farm.liquidity;
if(farm.version == 3){
(uint256 fees0Collected, uint256 fees1Collected) = _collectV3(farm.tokenId);
farm.accFees0PerShare += fees0Collected * CALC_PRECISION / farm.liquidity;
farm.accFees1PerShare += fees1Collected * CALC_PRECISION / farm.liquidity;
}
ThirdPartyReward storage thirdPartyReward = thirdPartyRewards[_farmId];
if(thirdPartyReward.token != address(0) && thirdPartyReward.tokensPerBlock > 0){
uint256 thirdPartyRewardTokens;
// Third party rewards ended between last calculation and now
if(farm.lastCalcBlock < thirdPartyReward.endBlock && thirdPartyReward.endBlock <= block.number){
uint256 blocksAbleToPay = thirdPartyReward.endBlock - farm.lastCalcBlock;
thirdPartyRewardTokens = blocksAbleToPay * thirdPartyReward.tokensPerBlock;
thirdPartyReward.tokensPerBlock = 0;
}
else {
thirdPartyRewardTokens = blocksSinceLastCalc * thirdPartyReward.tokensPerBlock;
}
farm.accThirdPartyRewardsPerShare += thirdPartyRewardTokens * CALC_PRECISION / farm.liquidity;
}
farm.lastCalcBlock = block.number;
}
// Internal function that calculates duration bonus multiplier
function _calculateDurationMultiplier(uint256 blocksServed) internal view returns (uint256) {
// Occurs when farmer is undefined
if(blocksServed == block.number){
return 10000;
}
// If blockCount is greater than or equal to maxBlockCount, return 25000
else if (blocksServed >= MAX_BLOCKS_FOR_BONUS) {
return 25000;
}
// Calculate the linear yield
return 10000 + (15000 * blocksServed) / MAX_BLOCKS_FOR_BONUS;
}
struct Position {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint128 liquidity;
}
function _getPosition(uint256 _tokenId) internal view returns (Position memory) {
(, ,address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, , , ,) = positionManager.positions(_tokenId);
return Position({
token0: token0,
token1: token1,
fee: fee,
tickLower: tickLower,
tickUpper: tickUpper,
liquidity: liquidity
});
}
// Remove farmer address from a farm
function _removeFarmer(uint256 _farmId, address _address) internal {
Farm storage farm = farmInfo[_farmId];
uint farmerCount = farm.farmers.length;
uint indexToRemove = farmerCount;
for (uint i = 0; i < farmerCount; i++) {
if (farm.farmers[i] == _address) {
indexToRemove = i;
break;
}
}
if (indexToRemove < farmerCount) {
farm.farmers[indexToRemove] = farm.farmers[farmerCount - 1];
farm.farmers.pop();
farm.farmerCount -= 1;
}
}
/** ==================== EXTERNAL HELPER FUNCTIONS ==================== */
// Exposes function that calculates duration bonus multiplier - for gas savings
function calculateDurationMultiplier(uint256 blocksServed) external view returns (uint256) {
return _calculateDurationMultiplier(blocksServed);
}
// At least one farmer should be active when depositing, otherwise third party rewards go unaccounted for
function depositThirdPartyReward(uint256 _farmId, uint256 _amountToken, uint256 _distributeOverBlockCount) external {
ThirdPartyReward storage thirdPartyReward = thirdPartyRewards[_farmId];
require(thirdPartyReward.token != address(0), "Third party reward not defined");
require(owner() == msg.sender || thirdPartyReward.tokenManager == msg.sender, "Only token manager can deposit");
_updateFarmRewardsAndFees(_farmId);
IERC20(thirdPartyReward.token).transferFrom(msg.sender, address(this), _amountToken);
uint256 blocksTillEnd = thirdPartyReward.endBlock >= block.number ? thirdPartyReward.endBlock - block.number : 0;
uint256 unaccountedTokens = blocksTillEnd * thirdPartyReward.tokensPerBlock;
thirdPartyReward.tokensPerBlock = (unaccountedTokens + _amountToken) / _distributeOverBlockCount;
thirdPartyReward.endBlock = block.number + _distributeOverBlockCount;
emit ThirdPartyRewardDeposited(_farmId, thirdPartyReward.token, thirdPartyReward.tokensPerBlock, thirdPartyReward.endBlock);
}
// Update the third party reward manager for a farm. Can only be called by the ElectroSwap or Third-Party token manager.
function updateThirdPartyRewardManager(uint256 _farmId, address _tokenManager) external {
require(_tokenManager != address(0), "Cannot be zero address");
require(owner() == msg.sender || thirdPartyRewards[_farmId].tokenManager == msg.sender, "Only token manager can update");
thirdPartyRewards[_farmId].tokenManager = _tokenManager;
emit ThirdPartyRewardUpdated(_farmId, _tokenManager);
}
function getFarmById(uint256 _farmId) external view returns (Farm memory farm) {
farm = farmInfo[_farmId];
uint256 blocksSinceLastCalc = block.number - farm.lastCalcBlock;
if(farm.liquidity > 0){
uint256 rewards = blocksSinceLastCalc * ((rewardPerBlock * farm.allocPoint) / totalAllocPoint);
farm.accRewardsPerShare += rewards * CALC_PRECISION / farm.liquidity;
if(farm.version == 3){
(uint256 uncollectedFee0, uint256 uncollectedFee1) = feeHelper.getUncollectedFees(farm.tokenId);
farm.accFees0PerShare += uncollectedFee0 * CALC_PRECISION / farm.liquidity;
farm.accFees1PerShare += uncollectedFee1 * CALC_PRECISION / farm.liquidity;
}
if(thirdPartyRewards[_farmId].token != address(0)){
uint256 thirdPartyRewardTokens = blocksSinceLastCalc * thirdPartyRewards[_farmId].tokensPerBlock;
farm.accThirdPartyRewardsPerShare += thirdPartyRewardTokens * CALC_PRECISION / farm.liquidity;
}
}
// Prevent large responses by sending empty farmer array
address[] memory emptyArr;
farm.farmers = emptyArr;
}
function getFarmIdsByPoolAddress(address _poolAddr) external view returns (uint256[] memory) {
return poolFarmIds[_poolAddr];
}
function getFarmerByFarmIdAndIndex(uint256 _farmId, uint256 _farmerIndex) external view returns (Farmer memory) {
address farmerAddress = farmInfo[_farmId].farmers[_farmerIndex];
return getFarmerByFarmIdAndAddress(_farmId, farmerAddress);
}
function getFarmerByFarmIdAndAddress(uint256 _farmId, address _farmerAddress) public view returns (Farmer memory farmer) {
Farm memory farm = farmInfo[_farmId];
farmer = farmerInfo[_farmId][_farmerAddress];
farmer.durationMultiplier = _calculateDurationMultiplier(block.number - farmer.startingBlock);
uint256 blocksSinceLastCalc = block.number - farm.lastCalcBlock;
if(farm.liquidity > 0){
uint256 rewards = blocksSinceLastCalc * ((rewardPerBlock * farm.allocPoint) / totalAllocPoint);
farm.accRewardsPerShare += rewards * CALC_PRECISION / farm.liquidity;
}
uint256 rewardsEarned = (farmer.liquidity * farm.accRewardsPerShare / CALC_PRECISION) - farmer.rewardDebt;
farmer.rewards = (rewardsEarned * farmer.boltMultiplier * farmer.durationMultiplier) / 100000000;
ThirdPartyReward memory thirdPartyReward = thirdPartyRewards[_farmId];
if(thirdPartyReward.token != address(0)){
if(farm.liquidity > 0){
uint256 thirdPartyRewardTokens;
if(farm.lastCalcBlock < thirdPartyReward.endBlock && thirdPartyReward.endBlock <= block.number){
uint256 blocksAbleToPay = thirdPartyReward.endBlock - farm.lastCalcBlock;
thirdPartyRewardTokens = blocksAbleToPay * thirdPartyReward.tokensPerBlock;
}
else {
thirdPartyRewardTokens = blocksSinceLastCalc * thirdPartyReward.tokensPerBlock;
}
farm.accThirdPartyRewardsPerShare += thirdPartyRewardTokens * CALC_PRECISION / farm.liquidity;
}
farmer.thirdPartyRewards = (farmer.liquidity * farm.accThirdPartyRewardsPerShare / CALC_PRECISION) - farmer.thirdPartyRewardDebt;
}
if(farm.version == 3){
if(farm.liquidity > 0){
(uint256 uncollectedFee0, uint256 uncollectedFee1) = feeHelper.getUncollectedFees(farm.tokenId);
farm.accFees0PerShare += uncollectedFee0 * CALC_PRECISION / farm.liquidity;
farm.accFees1PerShare += uncollectedFee1 * CALC_PRECISION / farm.liquidity;
}
farmer.fees0 = (farmer.liquidity * farm.accFees0PerShare / CALC_PRECISION) - farmer.fees0Debt;
farmer.fees1 = (farmer.liquidity * farm.accFees1PerShare / CALC_PRECISION) - farmer.fees1Debt;
}
}
function getThirdPartyRewardConfigByFarmId(uint256 _farmId) external view returns (ThirdPartyReward memory) {
return thirdPartyRewards[_farmId];
}
/** ==================== EVENTS ==================== */
event FarmCreated(uint256 indexed farmId, address indexed poolAddr, uint256 version, uint256 tokenId, uint256 allocPoints, uint256 totalAllocPoints);
event FarmDeactivated(uint256 indexed farmId, address indexed poolAddr, uint256 version, uint256 tokenId);
event FarmAllocationUpdated(uint256 indexed farmId, address indexed poolAddr, uint256 allocPoints, uint256 totalAllocPoints);
event FarmNameUpdated(uint256 indexed farmId, address indexed poolAddr, string _name);
event FarmDeposit(uint256 indexed farmId, address indexed farmer, uint256 amount0Added, uint256 amount1Added, uint256 liquidityAdded);
event FarmIncrease(uint256 indexed farmId, address indexed farmer, uint256 amount0Added, uint256 amount1Added, uint256 liquidityAdded, uint256 adjustedStartingBlock);
event FarmWithdrawl(uint256 indexed farmId, address indexed farmer, uint256 amount0Withdrawn, uint256 amount1Withdrawn, uint256 amountRewards, uint256 fees0Collected, uint256 fees1Collected, uint256 thirdPartyRewardsCollected);
event ThirdPartyRewardAdded(uint256 indexed farmId, address indexed rewardToken, address indexed tokenManager);
event ThirdPartyRewardUpdated(uint256 indexed farmId, address tokenManager);
event ThirdPartyRewardDeposited(uint256 indexed farmId, address indexed rewardToken, uint256 tokensPerBlock, uint256 endBlock);
event RewardsPerBlockUpdated(uint256 rewardPerBlock, uint256 prevRewardPerBlock);
event BoltMultiplierUpdated(uint256 amountBolt, uint256 multiplier);
function onERC721Received(address, address, uint256, bytes calldata) external pure returns (bytes4) {
return this.onERC721Received.selector;
}
receive() external payable {}
fallback() external payable {}
}