// SPDX-License-Identifier: MPL-2.0 pragma solidity ^0.8.17; import {IRewarder} from "./interfaces/IRewarder.sol"; import {IERC20Minter} from "./interfaces/IERC20Minter.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; /** * @dev Mint or transfer ERC20 tokens according to the reward configuration. * It's possible to limit total amount of tokens mintable by this contract. * The total of 150,000,000 $L7L would be allowed to be minted by this contract * to the team. */ contract ERC20Rewarder is IRewarder { using SafeERC20 for IERC20; bytes4 private constant IERC20_MINT_INTERFACE = 0x40c10f19; bytes4 private constant IERC20_INTERFACE = 0xffffffff; address public immutable CONFIGURATOR; IERC20 public REWARD_TOKEN; bytes4 public CLAIM_INTERFACE; address public TRUSTED_AUTHORIZED_CALLER1; address public TRUSTED_AUTHORIZED_CALLER2; IERC20Minter public MINTER; uint256 public MINT_CAP; uint256 public minted; event Rewarded( address indexed nftContract, uint256 indexed tokenId, address indexed user, bytes4 claimInterface, address recipient, uint256 amount, uint256 newLpAmount ); error InvalidConfig(); error Unauthorized(); error ExceededMintCap(); error InvalidAmount(); constructor() { CONFIGURATOR = msg.sender; } /** * @dev Called only once during configuration phase. * * @param _rewardToken ERC20 token which used for rewarding users. * @param _authorizedCaller1 Smart contract address which would issue rewards through this contract. * @param _authorizedCaller2 Smart contract address which would issue rewards through this contract. * @param _claimInterface ERC20 interface which is used for rewarding mint / transfer. * @param _minter Address of smart contract which has minting rights to reward token, pass address(0) if transfer is used instead. * @param _mintCap Maximum amount of tokens authorized to mint by this smart contract, 0 - no limitations. */ function immutableConfig( IERC20 _rewardToken, address _authorizedCaller1, address _authorizedCaller2, bytes4 _claimInterface, address _minter, uint256 _mintCap ) external { if (CONFIGURATOR != msg.sender) revert Unauthorized(); if (TRUSTED_AUTHORIZED_CALLER1 != address(0) || address(REWARD_TOKEN) != address(0)) revert Unauthorized(); if (address(_rewardToken) == address(0) || _authorizedCaller1 == address(0)) revert InvalidConfig(); TRUSTED_AUTHORIZED_CALLER1 = _authorizedCaller1; TRUSTED_AUTHORIZED_CALLER2 = _authorizedCaller2; REWARD_TOKEN = _rewardToken; if (_claimInterface == IERC20_MINT_INTERFACE) { if (_minter == address(0)) revert InvalidConfig(); MINTER = IERC20Minter(_minter); MINT_CAP = _mintCap; } else if (_claimInterface == IERC20_INTERFACE) { if (_minter != address(0)) revert InvalidConfig(); if (_mintCap > 0) revert InvalidConfig(); } else { revert InvalidConfig(); } CLAIM_INTERFACE = _claimInterface; } /** * @dev Placeholder is used, because reward ledger is handled by TRUSTED_AUTHORIZED_CALLERS. * * @param amount LP token amount. * @return tokens to be rewarded. * @return amounts to be rewarded. */ function pendingTokens(address, uint256, uint256 amount) external view returns (IERC20[] memory, uint256[] memory) { IERC20[] memory _tokens = new IERC20[](1); _tokens[0] = REWARD_TOKEN; uint256[] memory _amounts = new uint256[](1); _amounts[0] = amount; return (_tokens, _amounts); } /** * @dev Method where reward minting or transfer should happen. * it's expected that re-entrancy is handled by TRUSTED_AUTHORIZED_CALLERS. * * @param _nftContract The NFT contract of the pool. * @param _tokenId NFT token id. * @param _user Address which triggers reward distribution * @param _recipient reward beneficiary address. * @param _amount pending reward. * @param _newLpAmount total LP tokens which recieve reward. */ function onReward( address _nftContract, uint256 _tokenId, address _user, address _recipient, uint256 _amount, uint256 _newLpAmount ) external { if (msg.sender != TRUSTED_AUTHORIZED_CALLER1 && msg.sender != TRUSTED_AUTHORIZED_CALLER2) revert Unauthorized(); if (_amount <= 0) revert InvalidAmount(); bytes4 _claimInterface = CLAIM_INTERFACE; if (_claimInterface == IERC20_MINT_INTERFACE) { uint256 _mintCap = MINT_CAP; if (_mintCap > 0) { uint256 _newMinted = minted + _amount; if (_newMinted > MINT_CAP) revert ExceededMintCap(); minted = _newMinted; } MINTER.mint(_recipient, _amount); } else { REWARD_TOKEN.safeTransfer(_recipient, _amount); } emit Rewarded( _nftContract, _tokenId, _user, _claimInterface, _recipient, _amount, _newLpAmount ); } }