// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
// @title ArbSys
// @dev Globally available variables for Arbitrum may have both an L1 and an L2
// value, the ArbSys interface is used to retrieve the L2 value
interface ArbSys {
function arbBlockNumber() external view returns (uint256);
function arbBlockHash(uint256 blockNumber) external view returns (bytes32);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.20;
import "./ArbSys.sol";
// @title Chain
// @dev Wrap the calls to retrieve chain variables to handle differences
// between chain implementations
library Chain {
// if the ARBITRUM_CHAIN_ID changes, a new version of this library
// and contracts depending on it would need to be deployed
uint256 public constant ARBITRUM_CHAIN_ID = 42_161;
uint256 public constant ARBITRUM_GOERLI_CHAIN_ID = 421_613;
ArbSys public constant arbSys = ArbSys(address(100));
// @dev return the current block's timestamp
// @return the current block's timestamp
function currentTimestamp() internal view returns (uint256) {
return block.timestamp;
}
// @dev return the current block's number
// @return the current block's number
function currentBlockNumber() internal view returns (uint256) {
if (shouldUseArbSysValues()) {
return arbSys.arbBlockNumber();
}
return block.number;
}
// @dev return the current block's hash
// @return the current block's hash
function getBlockHash(uint256 blockNumber) internal view returns (bytes32) {
if (shouldUseArbSysValues()) {
return arbSys.arbBlockHash(blockNumber);
}
return blockhash(blockNumber);
}
function shouldUseArbSysValues() internal view returns (bool) {
return
block.chainid == ARBITRUM_CHAIN_ID || block.chainid == ARBITRUM_GOERLI_CHAIN_ID;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IAcceptComptroller } from "./interfaces/IAcceptComptroller.sol";
import { IComptroller } from "./interfaces/IComptroller.sol";
import { Errors } from "./libraries/Errors.sol";
//TODO : Should be change this with V2 of Comptroller
contract Comptroller is IComptroller {
address private pendingAdmin;
address private gmxFactory;
address private platformLogic;
address private exchangeRouter;
address private positionNft;
address private gmxAdapter;
address private router;
address private reader;
address private vault;
address private datastore;
address private callBackReceiver;
address private ethUsdAggregator;
address private arbRewardsClaimer;
address public override admin;
/// @notice the platform's referral code for GMX based transactions
bytes32 public override gmxReferralCode;
uint256 public maxCallbackGasLimit;
/// @notice Mapping to store the allowed tokens
/// @dev Can be used to stop exploits with a curcuit brake
/// @dev add a way to add to the mapping
/// @dev should store the allowed tokens for payment of the fee?
mapping(IERC20 => bool) public override allowedTokens;
mapping(address => bool) public override allowedCollateralTokens;
/// @notice Reverts if called by any account other than the admin.
modifier onlyAdmin() {
if (admin != msg.sender) {
revert Errors.Comptroller_CallerNotAdmin();
}
_;
}
/// @notice reverts if address given to it is 0x0
modifier notZeroAddress(address _address) {
if (_address == address(0x0)) {
revert Errors.Comptroller_AddressGivenIsZeroAddress();
}
_;
}
/// @notice reverts if given address is not initialized - is 0x0 address
modifier notUninitialized(address _address) {
if (_address == address(0)) {
revert Errors.Comptroller_AddressNotSet();
}
_;
}
/// @notice sets intitial contract address for codebase
/// @param initialAdmin The address of the initial contract admin
/// @param _router The address of GMX's Router contract
/// @param _vault The address of GMX's Vault contract
/// @param _router The address of GMX's Router contract
/// @param _platformLogic The address of Pear's PlatformLogic
/// @param _datastore the address of GMX's Datastore
constructor(
address initialAdmin,
address _reader,
address _vault,
address _router,
address _exchangeRouter,
address _platformLogic,
IERC20 token,
bytes32 _referralCode,
address _datastore,
address _ethUsdAggregator,
address _arbRewardsClaimer
) {
admin = initialAdmin;
router = _router;
reader = _reader;
vault = _vault;
platformLogic = _platformLogic;
exchangeRouter = _exchangeRouter;
allowedTokens[IERC20(token)] = true;
gmxReferralCode = _referralCode;
datastore = _datastore;
ethUsdAggregator = _ethUsdAggregator;
arbRewardsClaimer = _arbRewardsClaimer;
}
/// @inheritdoc IComptroller
function transferAdmin(address newAdmin) external override onlyAdmin {
// Effects: update the admin.
if (newAdmin == address(0)) {
revert Errors.Comptroller_AdminCannotBe0Address();
}
pendingAdmin = newAdmin;
// Log the transfer of the admin.
emit ComptrollerTransferAdmin({
oldAdmin: msg.sender,
newAdmin: newAdmin
});
}
/// @inheritdoc IComptroller
function acceptAdmin() external override {
address _sender = msg.sender;
if (pendingAdmin != _sender) {
revert Errors.Comptroller_UnauthorizedAccount(_sender);
}
emit ComptrollerAcceptNewAdmin({ oldAdmin: admin, newAdmin: msg.sender });
admin = _sender;
}
/// @inheritdoc IComptroller
function acceptComptroller(IAcceptComptroller _contractToChangeController)
external
override
onlyAdmin
{
_contractToChangeController.acceptComptroller();
}
/*//////////////////////////////////////////////////////////////////////////
SETTER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc IComptroller
function setAllowedToken(
IERC20 tokenFeePaymentAddress,
bool allowed
)
external
override
onlyAdmin
{
allowedTokens[tokenFeePaymentAddress] = allowed;
emit SetAllowedToken(tokenFeePaymentAddress, allowed);
}
/// @inheritdoc IComptroller
function setGmxFactory(address factory)
external
override
onlyAdmin
notZeroAddress(factory)
{
emit SetGmxFactory(gmxFactory, factory);
gmxFactory = factory;
}
/// @inheritdoc IComptroller
function setCallBackReceiver(address receiver)
external
override
onlyAdmin
notZeroAddress(receiver)
{
emit SetCallBackReceiver(callBackReceiver, receiver);
callBackReceiver = receiver;
}
/// @inheritdoc IComptroller
function setGmxAdapter(address adapter)
external
override
onlyAdmin
notZeroAddress(adapter)
{
emit SetGmxAdapter(gmxAdapter, adapter);
gmxAdapter = adapter;
}
/// @inheritdoc IComptroller
function setPlatformLogic(address _platformLogic)
external
override
onlyAdmin
notZeroAddress(_platformLogic)
{
emit SetPlatformLogic(platformLogic, _platformLogic);
platformLogic = _platformLogic;
}
/// @inheritdoc IComptroller
function setPositionNft(address _positionNft)
external
override
onlyAdmin
notZeroAddress(_positionNft)
{
emit SetPositionNft(positionNft, _positionNft);
positionNft = _positionNft;
}
/// @inheritdoc IComptroller
function setGmxReader(address _reader)
external
override
onlyAdmin
notZeroAddress(_reader)
{
emit SetGmxReader(reader, _reader);
reader = _reader;
}
/// @inheritdoc IComptroller
function setGmxVault(address _vault)
external
override
onlyAdmin
notZeroAddress(_vault)
{
emit SetGmxVault(vault, _vault);
vault = _vault;
}
/// @inheritdoc IComptroller
function setGmxRouter(address _router)
external
override
onlyAdmin
notZeroAddress(_router)
{
emit SetGmxRouter(router, _router);
router = _router;
}
function setGmxExchangeRouter(address _exchangeRouter)
external
override
onlyAdmin
notZeroAddress(_exchangeRouter)
{
emit SetGmxExchangeRouter(exchangeRouter, _exchangeRouter);
exchangeRouter = _exchangeRouter;
}
/// @inheritdoc IComptroller
function setReferralCode(bytes32 code) external override onlyAdmin {
emit SetReferralCode(gmxReferralCode, code);
gmxReferralCode = code;
}
/// @inheritdoc IComptroller
function setMaxCallbackGasLimit(uint256 _maxCallbackgasLimit)
external
override
onlyAdmin
{
emit SetMaxCallbackgasLimit(maxCallbackGasLimit, _maxCallbackgasLimit);
maxCallbackGasLimit = _maxCallbackgasLimit;
}
/// @inheritdoc IComptroller
function setEthUsdAggregator(address _ethUsdAggregator)
external
override
onlyAdmin
notZeroAddress(_ethUsdAggregator)
{
emit SetEthUsdAggregator(ethUsdAggregator, _ethUsdAggregator);
ethUsdAggregator = _ethUsdAggregator;
}
function setArbRewardsClaimer(address _arbRewardsClaimer)
external
override
onlyAdmin
notZeroAddress(_arbRewardsClaimer)
{
emit SetArbRewardsClaimer(arbRewardsClaimer, _arbRewardsClaimer);
arbRewardsClaimer = _arbRewardsClaimer;
}
/*//////////////////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @inheritdoc IComptroller
function getVault()
external
view
override
notUninitialized(vault)
returns (address vaultContract)
{
return vault;
}
/// @inheritdoc IComptroller
function getRouter()
external
view
override
notUninitialized(router)
returns (address routerContract)
{
return router;
}
/// @inheritdoc IComptroller
function getReader()
external
view
override
notUninitialized(reader)
returns (address readerContract)
{
return reader;
}
/// @inheritdoc IComptroller
function getDatastore()
external
view
override
notUninitialized(datastore)
returns (address datastoreContract)
{
return datastore;
}
/// @inheritdoc IComptroller
function getGmxFactory()
external
view
override
notUninitialized(gmxFactory)
returns (address factory)
{
return gmxFactory;
}
/// @inheritdoc IComptroller
function getCallBackReceiver()
external
view
override
notUninitialized(callBackReceiver)
returns (address receiver)
{
return callBackReceiver;
}
/// @inheritdoc IComptroller
function getGmxAdapter()
external
view
override
notUninitialized(gmxAdapter)
returns (address adapter)
{
return gmxAdapter;
}
/// @inheritdoc IComptroller
function getPlatformLogic()
external
view
override
notUninitialized(platformLogic)
returns (address platformLogicContract)
{
return platformLogic;
}
/// @inheritdoc IComptroller
function getPositionNft()
external
view
override
notUninitialized(positionNft)
returns (address positionNftContract)
{
return positionNft;
}
function getExchangeRouter()
external
view
override
notUninitialized(exchangeRouter)
returns (address exchangeRouterContract)
{
return exchangeRouter;
}
function getMaxCallBackLimit()
external
view
override
notUninitialized(exchangeRouter)
returns (uint256)
{
return maxCallbackGasLimit;
}
function getEthUsdAggregator()
external
view
override
notUninitialized(ethUsdAggregator)
returns (address aggregator)
{
return ethUsdAggregator;
}
function getArbRewardsClaimer()
external
view
override
notUninitialized(arbRewardsClaimer)
returns (address rewardsClaimer)
{
return arbRewardsClaimer;
}
}
// 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;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.20;
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS
}
/**
* @dev The signature derives the `address(0)`.
*/
error ECDSAInvalidSignature();
/**
* @dev The signature has an invalid length.
*/
error ECDSAInvalidSignatureLength(uint256 length);
/**
* @dev The signature has an S value that is in the upper half order.
*/
error ECDSAInvalidSignatureS(bytes32 s);
/**
* @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not
* return address(0) without also returning an error description. Errors are documented using an enum (error type)
* and a bytes32 providing additional information about the error.
*
* If no error is returned, then the address can be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError, bytes32) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length));
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM precompile allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*/
function tryRecover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address, RecoverError, bytes32) {
unchecked {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
// We do not check for an overflow here since the shift operation results in 0 or 1.
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*/
function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError, bytes32) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS, s);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature, bytes32(0));
}
return (signer, RecoverError.NoError, bytes32(0));
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) {
(address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s);
_throwError(error, errorArg);
return recovered;
}
/**
* @dev Optionally reverts with the corresponding custom error according to the `error` argument provided.
*/
function _throwError(RecoverError error, bytes32 errorArg) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert ECDSAInvalidSignature();
} else if (error == RecoverError.InvalidSignatureLength) {
revert ECDSAInvalidSignatureLength(uint256(errorArg));
} else if (error == RecoverError.InvalidSignatureS) {
revert ECDSAInvalidSignatureS(errorArg);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/EIP712.sol)
pragma solidity ^0.8.20;
import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
import {IERC5267} from "@openzeppelin/contracts/interfaces/IERC5267.sol";
import {Initializable} from "../../proxy/utils/Initializable.sol";
/**
* @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.
*
* The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose
* encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract
* does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to
* produce the hash of their typed data using a combination of `abi.encode` and `keccak256`.
*
* This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding
* scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA
* ({_hashTypedDataV4}).
*
* The implementation of the domain separator was designed to be as efficient as possible while still properly updating
* the chain id to protect against replay attacks on an eventual fork of the chain.
*
* NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method
* https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].
*
* NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain
* separator of the implementation contract. This will cause the {_domainSeparatorV4} function to always rebuild the
* separator from the immutable values, which is cheaper than accessing a cached version in cold storage.
*/
abstract contract EIP712Upgradeable is Initializable, IERC5267 {
bytes32 private constant TYPE_HASH =
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
/// @custom:storage-location erc7201:openzeppelin.storage.EIP712
struct EIP712Storage {
/// @custom:oz-renamed-from _HASHED_NAME
bytes32 _hashedName;
/// @custom:oz-renamed-from _HASHED_VERSION
bytes32 _hashedVersion;
string _name;
string _version;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.EIP712")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant EIP712StorageLocation = 0xa16a46d94261c7517cc8ff89f61c0ce93598e3c849801011dee649a6a557d100;
function _getEIP712Storage() private pure returns (EIP712Storage storage $) {
assembly {
$.slot := EIP712StorageLocation
}
}
/**
* @dev Initializes the domain separator and parameter caches.
*
* The meaning of `name` and `version` is specified in
* https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:
*
* - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.
* - `version`: the current major version of the signing domain.
*
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
function __EIP712_init(string memory name, string memory version) internal onlyInitializing {
__EIP712_init_unchained(name, version);
}
function __EIP712_init_unchained(string memory name, string memory version) internal onlyInitializing {
EIP712Storage storage $ = _getEIP712Storage();
$._name = name;
$._version = version;
// Reset prior values in storage if upgrading
$._hashedName = 0;
$._hashedVersion = 0;
}
/**
* @dev Returns the domain separator for the current chain.
*/
function _domainSeparatorV4() internal view returns (bytes32) {
return _buildDomainSeparator();
}
function _buildDomainSeparator() private view returns (bytes32) {
return keccak256(abi.encode(TYPE_HASH, _EIP712NameHash(), _EIP712VersionHash(), block.chainid, address(this)));
}
/**
* @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this
* function returns the hash of the fully encoded EIP712 message for this domain.
*
* This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:
*
* ```solidity
* bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(
* keccak256("Mail(address to,string contents)"),
* mailTo,
* keccak256(bytes(mailContents))
* )));
* address signer = ECDSA.recover(digest, signature);
* ```
*/
function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {
return MessageHashUtils.toTypedDataHash(_domainSeparatorV4(), structHash);
}
/**
* @dev See {IERC-5267}.
*/
function eip712Domain()
public
view
virtual
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
)
{
EIP712Storage storage $ = _getEIP712Storage();
// If the hashed name and version in storage are non-zero, the contract hasn't been properly initialized
// and the EIP712 domain is not reliable, as it will be missing name and version.
require($._hashedName == 0 && $._hashedVersion == 0, "EIP712: Uninitialized");
return (
hex"0f", // 01111
_EIP712Name(),
_EIP712Version(),
block.chainid,
address(this),
bytes32(0),
new uint256[](0)
);
}
/**
* @dev The name parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Name() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._name;
}
/**
* @dev The version parameter for the EIP712 domain.
*
* NOTE: This function reads from storage by default, but can be redefined to return a constant value if gas costs
* are a concern.
*/
function _EIP712Version() internal view virtual returns (string memory) {
EIP712Storage storage $ = _getEIP712Storage();
return $._version;
}
/**
* @dev The hash of the name parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Name` instead.
*/
function _EIP712NameHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory name = _EIP712Name();
if (bytes(name).length > 0) {
return keccak256(bytes(name));
} else {
// If the name is empty, the contract may have been upgraded without initializing the new storage.
// We return the name hash in storage if non-zero, otherwise we assume the name is empty by design.
bytes32 hashedName = $._hashedName;
if (hashedName != 0) {
return hashedName;
} else {
return keccak256("");
}
}
}
/**
* @dev The hash of the version parameter for the EIP712 domain.
*
* NOTE: In previous versions this function was virtual. In this version you should override `_EIP712Version` instead.
*/
function _EIP712VersionHash() internal view returns (bytes32) {
EIP712Storage storage $ = _getEIP712Storage();
string memory version = _EIP712Version();
if (bytes(version).length > 0) {
return keccak256(bytes(version));
} else {
// If the version is empty, the contract may have been upgraded without initializing the new storage.
// We return the version hash in storage if non-zero, otherwise we assume the version is empty by design.
bytes32 hashedVersion = $._hashedVersion;
if (hashedVersion != 0) {
return hashedVersion;
} else {
return keccak256("");
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
library Errors {
/*//////////////////////////////////////////////////////////////////////////
GMX FACTORY ERRORS
//////////////////////////////////////////////////////////////////////////*/
error GmxFactory_NotPlatformLogic();
error GmxFactory_TransactionFailedOnTokenTransfer();
error GmxFactory_InsufficientGmxExecutionFee();
error GmxFactory_TokenNotAllowed();
error GmxFactory_DifferentCollateralToken();
error GmxFactory_IncorrectGrossFeeAmount();
error GmxFactory_HigherSizeDelta();
error GmxFactory_NotOwner();
error GmxFactory_NotAdapterOwner();
error GmxFactory_PositionNotOpened();
error GmxFactory_TransferFailed();
error GmxFactory_NotNftHandler();
error GmxFactory_NotPositionRouter();
error GmxFactory_NotAdapter();
error GmxFactory_NotGmxPositionRouter();
error GmxFactory_NotCallBackReceiver();
error GmxFactory_NotEnoughFeeFunds();
error GmxFactory_SameIndexToken();
error GmxFactory_NotComptrollerAdmin();
error GmxFactory_DifferentPath();
error GmxFactory_EntityCannotBe0Address();
error GmxFactory_NotProposedComptroller();
error GmxFactory_PreviosOrderPending();
error GmxFactory_NotPositionNFT();
error GmxFactory_UnknownOrderKey();
/*//////////////////////////////////////////////////////////////////////////
VERTEX FACTORY ERRORS
//////////////////////////////////////////////////////////////////////////*/
error VertexFactory_NotPlatformLogic();
error VertexFactory_TokenNotAllowed();
error VertexFactory_NotComptrollerAdmin();
error VertexFactory_NotProposedComptroller();
error VertexFactory_EntityCannotBe0Address();
error VertexFactory_WrongValueSent(
uint256 valueSent, uint256 expectedFeeAmount
);
error VertexFactory_NotCallbackWallet();
error VertexFactoryCallback_FailedToSendFundsToUser();
error VertexFactoryCallback_FailedToSendFundsToCallbackWallet();
/*//////////////////////////////////////////////////////////////////////////
PLATFORM LOGIC ERRORS
//////////////////////////////////////////////////////////////////////////*/
error PlatformLogic_Unavailable();
error PlatformLogic_NotFactory();
error PlatformLogic_WrongFeeAmount();
error PlatformLogic_GivenZeroAddress();
error PlatformLogic_ExceedsAllowance(uint256 feeAmount);
error PlatformLogic_NotEnoughBalance();
error PlatformLogic_AddressSetInComptrollerIsNotThisOne();
error PlatformLogic_FeeAmountCannotBeNull();
error PlatformLogic_NotComptrollerAdmin();
error PlatformLogic_InvalidSigner();
error PlatformLogic_CodeCreatorIsNotMsgSender();
error PlatformLogic_RefereeNotMsgSender();
error PlatformLogic_ComptrollerCannot0BeAddress();
error PlatformLogic_TransactionFailed();
error PlatformLogic_WrongValueSent(
uint256 expectedFeeAmount, uint256 feeAmountSent
);
error PlatformLogic_ExceedingBps();
error PlatformLogic_NotPositionNFT();
/*//////////////////////////////////////////////////////////////////////////
GXM ADAPTER ERRORS
//////////////////////////////////////////////////////////////////////////*/
error GmxAdapter_IncorrectFeeAmount();
error GmxAdapter_WithdrawFailed();
error GmxAdapter_Unauthorized();
error GmxAdapter_CannotBeZeroAddress();
error GmxAdapter_NotComptrollerAdmin();
error GmxAdapter_NotAdapterOwner();
error GmxAdapter_NotGmxFactory();
error GmxAdapter_NotPositionNFT();
/*//////////////////////////////////////////////////////////////////////////
COMPTROLLER ERRORS
//////////////////////////////////////////////////////////////////////////*/
error Comptroller_CallerNotAdmin();
error Comptroller_AddressNotSet();
error Comptroller_AdminCannotBe0Address();
error Comptroller_UnauthorizedAccount(address unauthorizedUser);
error Comptroller_AddressGivenIsZeroAddress();
/*//////////////////////////////////////////////////////////////////////////
REWARDSCLAIMER ERRORS
//////////////////////////////////////////////////////////////////////////*/
error RewardsClaimer_NotOwner();
error RewardsClaimer_NotPlatformLogic();
error RewardsClaimer_UserHasNoRewardsToClaimOrHasExceededClaimingAmount();
error RewardsClaimer_CannotClaimRewardsInTheSameBlock();
error RewardsClaimer_CannotSendTo0Address();
error RewardsClaimer_NotWhitelistedPlatform();
error RewardsClaimer_ExceedsMaxClaimForPlatform();
/*//////////////////////////////////////////////////////////////////////////
POSITIONNFT ERRORS
//////////////////////////////////////////////////////////////////////////*/
error PositionNFT_CallerIsNotNftHandler();
error PositionNft_NotComptrollerAdmin();
error PositionNFT_NotAdapterOwner();
error PositionNFT_PositionNonExistantOrAlreadyClosed();
error PositionNFT_PositionAlreadyMinted();
error PositionNFT_NotNftOwner();
error PositionNFT_PositionNotClosed();
error PositionNFT_NotPositionOwner(address positionOwner);
error PositionNFT_NotOwner();
error PositionNFT_NotComptrollerAdmin();
error PositionNFT_ComptrollerCannot0BeAddress();
error PositionNFT_NotProposedComptroller();
error PositionNFT_TokenNotAllowed();
error PositonNFT_UserHasAPendingPosition();
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/**
* @title IAcceptComptroller
* @notice Interface is used to provide the addresses with a way to call the
* acceptComptroller function
* @notice The acceptComptroller function is implemented accross all
* Comptroller complient contracts and is used as a two step ownership transfer
*/
interface IAcceptComptroller {
function acceptComptroller() external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IChainLinkAggregator {
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IDataStore {
// @dev get the bool value for the given key
// @param key the key of the value
// @return the bool value for the key
function getBool(bytes32 key) external view returns (bool);
// @dev get the address value for the given key
// @param key the key of the value
// @return the address value for the key
function getAddress(bytes32 key) external view returns (address);
// @dev get the uint value for the given key
// @param key the key of the value
// @return the uint value for the key
function getUint(bytes32 key) external view returns (uint256);
// @dev get the int value for the given key
// @param key the key of the value
// @return the int value for the key
function getInt(bytes32 key) external view returns (int256);
// @dev get the bytes32 value for the given key
// @param key the key of the value
// @return the bytes32 value for the key
function getBytes32(bytes32 key) external view returns (bytes32);
// @dev get the string value for the given key
// @param key the key of the value
// @return the string value for the key
function getString(bytes32 key) external view returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
/// @title IPearBase
/// @notice Base logic for all Pear contracts.
interface IPearBase {
/// @notice Marks the different execution state of a position
enum ExecutionState {
Idle,
Pending,
Success,
Failed
}
/// @notice Marks the status of a position
enum PositionStatus {
NotExecuted,
Opened,
Closed,
Transferred,
Liquidated
}
struct PositionData {
bool isLong;
address collateralToken;
address marketAddress;
bytes32 orderKey;
ExecutionState orderExecutionState;
bool isOrderMarketIncrease;
PositionStatus positionStatus;
}
struct PairPositionData {
PositionData long;
PositionData short;
uint256 timestamp;
}
struct CreateOrderArgs {
address initialCollateralToken;
address[] swapPath;
address marketAddress;
uint256 sizeDelta;
uint256 initialCollateralDeltaAmount;
uint256 minOut;
uint256 executionFee;
uint256 acceptablePrice;
uint256 triggerPrice;
}
struct OpenPositionsArgs {
CreateOrderArgs long;
CreateOrderArgs short;
uint256 totalAmountIn;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IComptroller } from "./IComptroller.sol";
import { IPearBase } from "./IPearBase.sol";
import { IReader } from "../interfaces/IReader.sol";
/**
* @dev Interface of the GMX Factory contract.
*/
interface IPearGmxFactoryV2 is IPearBase {
/*//////////////////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/*//////////////////////////////////////////////////////////////////////////
SETTER FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/**
* @dev Initializes the contract, setting up the initial state.
* @param _comptroller Address of the comptroller contract.
*/
function initialize(address _comptroller) external;
/*//////////////////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @dev Withdraw tokens from the contract.
/// @param token The address of the token to withdraw.
/// @param to The address to which tokens will be transferred.
/// @param amount The amount of tokens to withdraw.
function withdrawToken(
address token,
address to,
uint256 amount
)
external;
/// @notice Withdraw ETH from the contract.
/// @param to The address to which ETH will be transferred.
/// @param amount The amount of ETH to withdraw.
function withdrawETH(address to, uint256 amount) external;
function openPositions(
CreateOrderArgs memory long,
CreateOrderArgs memory short,
bytes32 referralCode
)
external
payable
returns (address adapter, bytes32 longOrderKey, bytes32 shortOrderKey);
function createIncreasePositions(
address adapter,
CreateOrderArgs memory long,
CreateOrderArgs memory short
)
external
payable
returns (bytes32 longOrderKey, bytes32 shortOrderKey);
/// @notice Create a Decrease Position request collateral size long and
/// short position
/// @param long DecreasePosition args
function createDecreasePositions(
address adapter,
CreateOrderArgs memory long,
CreateOrderArgs memory short
)
external
payable
returns (bytes32 longOrderKey, bytes32 shortOrderKey);
/// @notice Close opned Position
/// @param long CLosePositionsargs
function closePositions(
address adapter,
CreateOrderArgs memory long,
CreateOrderArgs memory short
)
external
payable
returns (bytes32 longOrderKey, bytes32 shortOrderKey);
function gmxOrderCallback(
bytes32 orderKey,
bool isExecuted,
bool isIncrease
)
external;
/*
/// @dev Update the mappings upon NFT Transfer.
/// @param oldOwner The address of the previous owner of the Position.
/// @param newOwner The address of the new owner of the Position.
/// @param adapter The position whose ownership is to be transferred.
/// @return true if the transfer of the ownership is successful.
*/
function updateOwner(
address oldOwner,
address newOwner,
address adapter
)
external
returns (bool);
/*
function tokenTransferPlatformLogic(
IERC20 _token,
address _from,
address _to,
uint256 _amount
)
external
returns (bool);
*/
function feeTransferPlatformLogic(uint256 _amount)
external
returns (bool);
/*//////////////////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
function adapterOwners(address adapter)
external
view
returns (address onwer);
/// @notice returns pair's position data
/// @param adapter address of adapter contract
function getPairPositionData(address adapter)
external
view
returns (PairPositionData memory pairData);
/// @notice Get the position details associated with a given adapter address
/// @param adapter address of adapter to fetch data for
/// @return An array containing the all the details associated with adapter
function getPosition(address adapter)
external
view
returns (IReader.Props[] memory);
function gmxOrderCallbackAtLiquidatiion(
bytes32 orderKey,
address adapter,
bool isLong
) external;
/// @notice Get the position details associated with a given adapter address
function isPendingOrdersForPosition(
address adapter,
bool isLong
)
external
view
returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IPlatformLogic {
/*//////////////////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////////////////*/
event BackendVerifierChanged(
address oldBackendVerifier, address newBackendVerifier
);
event ManagePositionFeeChanged(
uint256 oldManagePositionFee, uint256 newManagePositionFee
);
event PlatformFeeChanged(uint256 oldPlatformFee, uint256 newPlatformFee);
event MintFeeChanged(uint256 oldMintFee, uint256 newMintFee);
event RefereeDiscountChanged(
uint256 oldRefereeDiscount, uint256 newRefereeDiscount
);
event ReferrerFeeChanged(
uint256 oldReferrerFeeChanged, uint256 newReferrerFeeChanged
);
event PearTreasuryChanged(address oldTreasury, address newTreasury);
event PearStakingContractChanged(
address oldPearStakingContract, address newPearStakingContract
);
event TreasuryFeeSplitChanged(
uint256 oldTreasuryFee, uint256 newTreasuryFee
);
event ReferralCodeAdded(address indexed referrer, bytes32 code);
event ReferralCodeEdited(
address indexed referrer, bytes32 code, address admin
);
event Referred(
address indexed referrer, address indexed referee, uint256 amount
);
event RefereeAdded(address indexed referee, bytes32 code);
event RefereeEdited(address indexed referee, bytes32 code, address admin);
event FactorySet(address factory, bool state);
event PendingTokenWithdrawal(address indexed referrer, uint256 amount);
event PendingEthWithdrawal(address indexed referrer, uint256 amount);
event TokenWithdrawal(address indexed withdrawer, uint256 amount);
event EthWithdrawal(address indexed withdrawer, uint256 amount);
event FeesPaid(
address indexed user, uint256 feeAmount, uint256 grossAmountAfterFee
);
event FeesPaidToStakingContract(
address indexed stakingContract, uint256 feeAmount
);
event FeesPaidToPearTreasury(
address indexed pearTreasury, uint256 feeAmount
);
event SetRefereeFeeAmount(
address indexed positionHolder,
address indexed adapterAddress,
uint256 feeAmount,
bool isLong
);
event SetPendingReferrerFeeAmount(
address indexed adapter, address indexed referrer, uint256 amount
);
event ArbRewardsFeeSplitChanged(
uint256 oldArbRewardsFeeSplit, uint256 newArbRewardsFeeSplit
);
event PlatformLogic_AddedRewards(
address indexed user, uint256 indexed amount
);
/*//////////////////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////////////////*/
struct ReferralCode {
/// @notice address of the person wanting to create a referral code
address referrer;
/// @notice the bytes32 version of a referral code - converted by the
/// backend
bytes32 referralCode;
/// @notice the EIP-712 signature of all other fields in the
/// ReferralCode struct. For a referralCode to be valid, it must be
/// signed by the backendVerifier
bytes signature;
}
/*//////////////////////////////////////////////////////////////////////////
SETTERS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Sets the arb rewards is active or not.
function setGmxArbRewardActive(bool _isArbRewardActive) external;
/// @notice Sets the arb rewards is active or not.
function setVertexArbRewardActive(bool _isArbRewardActive) external;
/// @notice Sets the arb rewards is active or not.
function setSymmArbRewardActive(bool _isArbRewardActive) external;
/// @notice Sets a new backend verifier address.
/// @param _newBackendVerifier The new address to be used as the backend
/// verifier.
function setBackendVerifierAddress(address _newBackendVerifier) external;
/// @notice Sets the discount percentage for referees.
/// @param _refereeDiscount The new discount percentage for referees.
function setRefereeDiscount(uint256 _refereeDiscount) external;
/// @notice Sets the referral fee percentage.
/// @param _referrerFee The new referral fee percentage.
function setReferrerFee(uint256 _referrerFee) external;
/// @notice Sets the platform fee percentage.
/// @param _platformFee The new platform fee percentage.
function setPlatformFee(uint256 _platformFee) external;
/// @notice Sets the fee split percentage that goes to the treasury.
/// @param _treasuryFeeSplit The new treasury fee split percentage.
function setTreasuryFeeSplit(uint256 _treasuryFeeSplit) external;
/// @notice Sets the fee split percentage that goes to arb for platforms
/// rewards.
function setGmxArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit) external;
/// @notice Sets the fee split percentage that goes to arb for platforms
/// rewards.
function setVertexArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit)
external;
/// @notice Sets the fee split percentage that goes to arb for platforms
/// rewards.
function setSymmArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit) external;
/// @notice Sets a new Pear Treasury address.
/// @param _newTreasury The new address for the Pear Treasury.
function setPearTreasury(address payable _newTreasury) external;
/// @notice Sets a new Pear Staking Contract address.
/// @param _newStakingContract The new address for the Pear Staking
/// Contract.
function setPearStakingContract(address payable _newStakingContract)
external;
/// @notice Adds or removes a factory address.
/// @param _factory The factory address to be added or removed.
/// @param _state The state to set for the factory address (true for add,
/// false for remove).
function setFactory(address _factory, bool _state) external;
/// @notice Sets the mint fee for a specific functionality in the platform.
/// @param _mintFee The new mint fee.
function setMintPositionFee(uint256 _mintFee) external;
/*//////////////////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice Creates a new referral code.
/// @param _referralCode The referral code data to be created.
/// @return A boolean indicating success of the operation.
function createReferralCode(ReferralCode memory _referralCode)
external
returns (bool);
/// @notice Associates a referee with a referral code.
/// @param _referralCode The referral code to associate the referee with.
/// @param _referee The address of the referee being associated.
/// @return A boolean indicating success of the operation.
function addReferee(
bytes32 _referralCode,
address _referee
)
external
returns (bool);
/// @notice Associates a referee with a referral code.
/// @notice Can only be called by Gmx Factory
/// @param _referralCode The referral code to associate the referee with.
/// @param _referee The address of the referee being associated.
/// @return A boolean indicating success of the operation.
function addRefereeFromFactory(
bytes32 _referralCode,
address _referee
)
external
returns (bool);
/// @notice Edits the referral code of a referrer.
/// @param _referrer The address of the referrer whose code is being edited.
/// @param _referralCode The new referral code to be associated with the
/// referrer.
function editReferralCode(
address _referrer,
bytes32 _referralCode
)
external;
function getplatformFeeOfOrder(
address referee,
uint256 grossAmount
)
external
view
returns (uint256);
/// @notice Applies platform fee logic for Ethereum Vertex.
function applyPlatformFeeEthVertex(
address _referee,
uint256 _feeAmountAfterDiscountAndWithdrawal,
uint256 _feeAmountAfterDiscount,
uint256 _referrerWithdrawal,
bool _isReferralProgramApplied
)
external
payable;
/// @notice Applies mint fee logic for ETH GMX.
function applyMintFeeEthGmx(address _referee) external payable;
/// @notice Applies platform fee logic for ETH GMX.
/// @param adapter The address of the adapter contract for order
/// @param _referee The address of the user being charged the fee.
/// @param _grossAmount The total transaction amount before fees.
/// @param _factory The address of the factory implementing the logic.
/// @return feeAmount The amount of fee to be applied.
/// @return referrerWithdrawal The amount of fee for referrer
function applyPlatformFeeETHGmx(
address adapter,
address _referee,
uint256 _grossAmount,
address _factory
)
external
returns (uint256 feeAmount, uint256 referrerWithdrawal);
/// @notice Checks the amount of token fees pending withdrawal by a
/// referrer.
/// @param _referrer The address of the referrer.
/// @return The amount of fees pending withdrawal.
function checkPendingTokenWithdrawals(
address _referrer,
IERC20 _token
)
external
view
returns (uint256);
/// @notice Allows a user to withdraw their accumulated token fees.
/// @param _token The ERC20 token address for which the fees are withdrawn.
function withdrawTokenFees(IERC20 _token) external;
/// @notice Allows a user to withdraw their accumulated eth fees from
/// referral logic.
function withdrawEthFees() external;
/// @notice Calculates fees based on the provided amount and basis points.
/// @param _amount The amount on which the fee is to be calculated.
/// @param _bps Basis points used to calculate the fee.
/// @return The calculated fee amount.
function calculateFees(
uint256 _amount,
uint256 _bps
)
external
pure
returns (uint256);
/// @notice Edits the referral code of referred users.
/// @param _referrer The address of the referrer whose referred users' code
/// is being edited.
/// @param _referralCode The new referral code to be associated with the
/// referred users.
function editReferredUsers(
address _referrer,
bytes32 _referralCode
)
external;
/// @notice Splits fees between stakers and the treasure.
/// @param _referee The address of the user involved in the transaction.
/// @param _adapterAddress The address of the adapter involved in the
/// transaction.
/// @param _isLong isLong
/// @param _amount The amount of fees to be split.
function splitBetweenStakersAndTreasuryEth(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _amount
)
external;
/// @notice Adds token fees for withdrawal by a referrer.
/// @param _referrer The address of the referrer.
/// @param _amount The amount of fees to be added for withdrawal.
/// @param _token The ERC20 token address for which the fees are added.
function addTokenFeesForWithdrawal(
address _referrer,
uint256 _amount,
IERC20 _token
)
external;
/// @notice function to calculate the platform fee for a given user on
/// Vertex's side
function calculatePlatformFeeEthVertex(
address _referee,
uint256 _grossAmount
)
external
returns (
uint256 _feeAmountAfterDiscountAndWithdrawal,
uint256 _feeAmountAfterDiscount,
uint256 _referrerWithdrawal,
bool _isReferralProgramApplied
);
/// @notice Sets the fee amount for a referee.
/// @param _referee The address of the referee.
/// @param _adapterAddress The address of the adapter involved in the
/// transaction.
/// @param _isLong isLong
/// @param _feeAmount The fee amount to be set.
function setRefereeFeeAmount(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _feeAmount
)
external;
/// @notice Handles token amount when a position fails.
/// @param _referee The address of the user involved in the failed
/// transaction.
/// @param _adapterAddress The address of the adapter involved in the
/// transaction.
/// @param _isLong isLong
/// @param _feeAmount The fee amount involved in the failed transaction.
function handleTokenAmountWhenPositionHasFailed(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _feeAmount
)
external;
/*//////////////////////////////////////////////////////////////////////////
VIEW FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/
/// @notice View function to get the address used to sign and validate
/// referral codes.
function backendVerifier() external view returns (address);
/// @notice View function to get the platform fee as a percentage of the
/// margin size.
function platformFee() external view returns (uint256);
/// @notice View function to get the mint fee in USDC used in a specific
/// function.
function mintFeeInUsdc() external view returns (uint256);
/// @notice View function to get the percentage of the platform fee
/// allocated to referrers.
function referrerFee() external view returns (uint256);
/// @notice View function to get the discount percentage for referees off
/// the platform fee.
function refereeDiscount() external view returns (uint256);
/// @notice View function to get the portion of fees sent to the Pear
/// Treasury.
function treasuryFeeSplit() external view returns (uint256);
/// @notice View function to get the arb rewards is active or not.
function isGmxArbRewardActive() external view returns (bool);
/// @notice View function to get the arb rewards is active or not.
function isVertexArbRewardActive() external view returns (bool);
/// @notice View function to get the arb rewards is active or not.
function isSymmArbRewardActive() external view returns (bool);
/// @notice View function to get the % of ArbRewardsFeeSplit - e.g 7000 -
/// 70%
function gmxArbRewardsFeeSplit() external view returns (uint256);
/// @notice View function to get the % of ArbRewardsFeeSplit - e.g 7000 -
/// 70%
function vertexArbRewardsFeeSplit() external view returns (uint256);
/// @notice View function to get the % of ArbRewardsFeeSplit - e.g 7000 -
/// 70%
function symmArbRewardsFeeSplit() external view returns (uint256);
/// @notice View function to get the address of the Pear Treasury.
function PearTreasury() external view returns (address payable);
/// @notice View function to get the address of the Pear Staking Contract.
function PearStakingContract() external view returns (address payable);
/// @notice Checks if the given address is a registered factory.
/// @param _factory The address to check.
/// @return A boolean indicating whether the address is a registered
/// factory.
function checkFactory(address _factory) external view returns (bool);
/// @notice Retrieves the current platform fee.
/// @return The platform fee as a percentage.
function viewPlatformFee() external view returns (uint256);
/// @notice Retrieves the current referee discount.
/// @return The discount percentage for referees.
function viewRefereeDiscount() external view returns (uint256);
/// @notice Retrieves the owner of a specific referral code.
/// @param _referralCode The referral code to check.
/// @return codeOwner The address of the owner of the referral code.
function viewReferralCodeOwner(bytes32 _referralCode)
external
view
returns (address codeOwner);
/// @notice Retrieves the referral code associated with a referrer.
/// @param _referrer The address of the referrer.
/// @return code The referral code associated with the referrer.
function viewReferrersCode(address _referrer)
external
view
returns (bytes32 code);
/// @notice Retrieves the referral code used by a referred user.
/// @param _referredUser The address of the referred user.
/// @return code The referral code used by the referred user.
function viewReferredUser(address _referredUser)
external
view
returns (bytes32 code);
/// @notice Retrieves the fee amount set for a referee.
/// @param _referee The address of the referee.
/// @param _adapterAddress The address of the adapter involved in the
/// transaction.
/// @param _isLong isLong
/// @return The fee amount set for the referee.
function viewRefereeFeeAmount(
address _referee,
address _adapterAddress,
bool _isLong
)
external
view
returns (uint256);
/// @notice Checks who referred a given user.
/// @param _referredUser The address of the user being checked.
/// @return referrer The address of the referrer.
function checkReferredUser(address _referredUser)
external
view
returns (address referrer);
/// @notice Retrieves the current chain ID.
/// @return The chain ID of the current blockchain.
function getChainID() external view returns (uint256);
/// @notice Retrieves the current referrer fee.
/// @return The referrer fee as a percentage.
function viewReferrerFee() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IDataStore } from "./IDataStore.sol";
import { Order } from "./order/Order.sol";
/**
* @dev Interface for the Position Reader contract of GMX.
*/
interface IReader {
struct Props {
Addresses addresses;
Numbers numbers;
Flags flags;
}
// @param account the position's account
// @param market the position's market
// @param collateralToken the position's collateralToken
struct Addresses {
address account;
address market;
address collateralToken;
}
// @param sizeInUsd the position's size in USD
// @param sizeInTokens the position's size in tokens
// @param collateralAmount the amount of collateralToken for collateral
// @param borrowingFactor the position's borrowing factor
// @param fundingFeeAmountPerSize the position's funding fee per size
// @param longTokenClaimableFundingAmountPerSize the position's claimable
// funding amount per size
// for the market.longToken
// @param shortTokenClaimableFundingAmountPerSize the position's claimable
// funding amount per size
// for the market.shortToken
// @param increasedAtBlock the block at which the position was last
// increased
// @param decreasedAtBlock the block at which the position was last
// decreased
struct Numbers {
uint256 sizeInUsd;
uint256 sizeInTokens;
uint256 collateralAmount;
uint256 borrowingFactor;
uint256 fundingFeeAmountPerSize;
uint256 longTokenClaimableFundingAmountPerSize;
uint256 shortTokenClaimableFundingAmountPerSize;
uint256 increasedAtBlock;
uint256 decreasedAtBlock;
}
// @param isLong whether the position is a long or short
struct Flags {
bool isLong;
}
/**
* @dev Get the positions of an account in a specific vault for multiple
* collateral and index tokens.
*
* @param dataStore datastore for gmx v2
* @param key position key
*/
function getPosition(
IDataStore dataStore,
bytes32 key
)
external
view
returns (Props memory);
function getOrder(
IDataStore dataStore,
bytes32 key
)
external
view
returns (Order.Props memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IRewardsClaimer {
// Events
/// @notice event for when rewards are claimed
event ClaimedRewards(
address indexed user, bytes32 indexed platform, uint256 amount
);
/// @notice event for when rewards are added
event AddedRewards(
address indexed user, bytes32 indexed platform, uint256 amount
);
/// @notice event for when arbUsdAggregator is changed
event ArbUsdAggregatorChanged(
address oldArbUsdAggregator, address newArbUsdAggregator
);
/// @notice event for when reward token decimals are deposited
event RewardTokensDeposited(
uint256 totalAmount,
uint256 amountForIsolated,
uint256 amountForCross,
uint256 amountForIntent
);
/// @notice event for when there aren't rewards added due to limit
event NoRewardsAddedDueToLimit(address user, bytes32 platform);
/// @notice External functions to query mapping of last reward block of user
function lastRewardBlock(address user) external view returns (uint256);
/// @notice External functions to query mapping of pending rewards
function getPendingRewards(
address _user,
bytes32 _platform
)
external
view
returns (uint256);
/// @notice External functions to query mapping of claimed rewards
function userClaimedRewardsAcrossAllPlatforms(address user)
external
view
returns (uint256);
/// @notice function to get the reward token
function rewardToken() external view returns (address);
/// @notice External function to get the arbUsdAggregator
function arbUsdAggregator() external view returns (address);
/// @notice External function to get the reward token decimals
function rewardTokenDecimals() external view returns (uint128);
/// @notice External function to get the max claim per wallet possible
function maxClaimPerWallet() external view returns (uint128);
/// @notice External function to get the total rewards
function totalUnclaimedRewards(bytes32 platform)
external
view
returns (uint256);
/// @notice External function to add rewards from platform logic
function addPendingUserRewards(
address _user,
uint256 _feeAmountInEth,
bytes32 _platform
)
external
returns (uint256 arbAmount);
/// @notice External function to claim rewards
function claimRewards(bytes32 _platform) external;
/// @notice External view function to check the possible rewards of a user
/// left
function checkRewards(
address _user,
bytes32 _platform
)
external
view
returns (uint256);
/// @notice External view function to get the reward tokens left in the
/// contract
function rewardTokensLeft() external view returns (uint256);
/// @notice Admin functions
function setArbUsdAggregator(address _arbUsdAggregator) external;
/// @notice function to pause the contract
function pause() external;
/// @notice function to unpause the contract
function unPause() external;
/// @notice function to set reward token decimals
function setRewardTokenDecimals(uint128 _rewardTokenDecimals) external;
/// @notice function to withdraw tokens as owner
function withdrawTokens(address _to, uint256 _amount) external;
/// @notice function to withdraw non reward tokens to prevent them from
/// being stuck in the contract
function withdrawNonRewardTokens(
address _to,
uint256 _amount,
address _token
)
external;
/// @dev admins needs to call approve beforehand
function depositRewardTokens(
uint256 _amountForIsolated,
uint256 _amountForCross,
uint256 _amountForIntent
)
external;
/// @notice function to edit max arb rewards to be spend
function editMaxArbRewardsToBeSpend(
bytes32 _platform,
uint256 _amount
)
external;
/// @notice function to edit whitelisted platforms, with onlyAdmin modifier
function editWhitelistedPlatforms(
bytes32 _platform,
bool _whitelisted
)
external;
/// @notice function to set max claim per wallet
function setMaxClaimPerWallet(uint128 _maxClaimPerWallet) external;
/// @notice function to set reward token
function setRewardToken(address _rewardToken) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.20;
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```solidity
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
*
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Storage of the initializable contract.
*
* It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
* when using with upgradeable contracts.
*
* @custom:storage-location erc7201:openzeppelin.storage.Initializable
*/
struct InitializableStorage {
/**
* @dev Indicates that the contract has been initialized.
*/
uint64 _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool _initializing;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;
/**
* @dev The contract is already initialized.
*/
error InvalidInitialization();
/**
* @dev The contract is not initializing.
*/
error NotInitializing();
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint64 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
* number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
* production.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
// Cache values to avoid duplicated sloads
bool isTopLevelCall = !$._initializing;
uint64 initialized = $._initialized;
// Allowed calls:
// - initialSetup: the contract is not in the initializing state and no previous version was
// initialized
// - construction: the contract is initialized at version 1 (no reininitialization) and the
// current contract is just being deployed
bool initialSetup = initialized == 0 && isTopLevelCall;
bool construction = initialized == 1 && address(this).code.length == 0;
if (!initialSetup && !construction) {
revert InvalidInitialization();
}
$._initialized = 1;
if (isTopLevelCall) {
$._initializing = true;
}
_;
if (isTopLevelCall) {
$._initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint64 version) {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing || $._initialized >= version) {
revert InvalidInitialization();
}
$._initialized = version;
$._initializing = true;
_;
$._initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
_checkInitializing();
_;
}
/**
* @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
*/
function _checkInitializing() internal view virtual {
if (!_isInitializing()) {
revert NotInitializing();
}
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
// solhint-disable-next-line var-name-mixedcase
InitializableStorage storage $ = _getInitializableStorage();
if ($._initializing) {
revert InvalidInitialization();
}
if ($._initialized != type(uint64).max) {
$._initialized = type(uint64).max;
emit Initialized(type(uint64).max);
}
}
/**
* @dev Returns the highest version that has been initialized. See {reinitializer}.
*/
function _getInitializedVersion() internal view returns (uint64) {
return _getInitializableStorage()._initialized;
}
/**
* @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
*/
function _isInitializing() internal view returns (bool) {
return _getInitializableStorage()._initializing;
}
/**
* @dev Returns a pointer to the storage namespace.
*/
// solhint-disable-next-line var-name-mixedcase
function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
assembly {
$.slot := INITIALIZABLE_STORAGE
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MerkleProof.sol)
pragma solidity ^0.8.20;
/**
* @dev These functions deal with verification of Merkle Tree proofs.
*
* The tree and the proofs can be generated using our
* https://github.com/OpenZeppelin/merkle-tree[JavaScript library].
* You will find a quickstart guide in the readme.
*
* WARNING: You should avoid using leaf values that are 64 bytes long prior to
* hashing, or use a hash function other than keccak256 for hashing leaves.
* This is because the concatenation of a sorted pair of internal nodes in
* the Merkle tree could be reinterpreted as a leaf value.
* OpenZeppelin's JavaScript library generates Merkle trees that are safe
* against this attack out of the box.
*/
library MerkleProof {
/**
*@dev The multiproof provided is not valid.
*/
error MerkleProofInvalidMultiproof();
/**
* @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree
* defined by `root`. For this, a `proof` must be provided, containing
* sibling hashes on the branch from the leaf to the root of the tree. Each
* pair of leaves and each pair of pre-images are assumed to be sorted.
*/
function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProof(proof, leaf) == root;
}
/**
* @dev Calldata version of {verify}
*/
function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {
return processProofCalldata(proof, leaf) == root;
}
/**
* @dev Returns the rebuilt hash obtained by traversing a Merkle tree up
* from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt
* hash matches the root of the tree. When processing the proof, the pairs
* of leafs & pre-images are assumed to be sorted.
*/
function processProof(bytes32[] memory proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Calldata version of {processProof}
*/
function processProofCalldata(bytes32[] calldata proof, bytes32 leaf) internal pure returns (bytes32) {
bytes32 computedHash = leaf;
for (uint256 i = 0; i < proof.length; i++) {
computedHash = _hashPair(computedHash, proof[i]);
}
return computedHash;
}
/**
* @dev Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by
* `root`, according to `proof` and `proofFlags` as described in {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerify(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProof(proof, proofFlags, leaves) == root;
}
/**
* @dev Calldata version of {multiProofVerify}
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function multiProofVerifyCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32 root,
bytes32[] memory leaves
) internal pure returns (bool) {
return processMultiProofCalldata(proof, proofFlags, leaves) == root;
}
/**
* @dev Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction
* proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another
* leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false
* respectively.
*
* CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree
* is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the
* tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer).
*/
function processMultiProof(
bytes32[] memory proof,
bool[] memory proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Calldata version of {processMultiProof}.
*
* CAUTION: Not all Merkle trees admit multiproofs. See {processMultiProof} for details.
*/
function processMultiProofCalldata(
bytes32[] calldata proof,
bool[] calldata proofFlags,
bytes32[] memory leaves
) internal pure returns (bytes32 merkleRoot) {
// This function rebuilds the root hash by traversing the tree up from the leaves. The root is rebuilt by
// consuming and producing values on a queue. The queue starts with the `leaves` array, then goes onto the
// `hashes` array. At the end of the process, the last hash in the `hashes` array should contain the root of
// the Merkle tree.
uint256 leavesLen = leaves.length;
uint256 proofLen = proof.length;
uint256 totalHashes = proofFlags.length;
// Check proof validity.
if (leavesLen + proofLen != totalHashes + 1) {
revert MerkleProofInvalidMultiproof();
}
// The xxxPos values are "pointers" to the next value to consume in each array. All accesses are done using
// `xxx[xxxPos++]`, which return the current value and increment the pointer, thus mimicking a queue's "pop".
bytes32[] memory hashes = new bytes32[](totalHashes);
uint256 leafPos = 0;
uint256 hashPos = 0;
uint256 proofPos = 0;
// At each step, we compute the next hash using two values:
// - a value from the "main queue". If not all leaves have been consumed, we get the next leaf, otherwise we
// get the next hash.
// - depending on the flag, either another value from the "main queue" (merging branches) or an element from the
// `proof` array.
for (uint256 i = 0; i < totalHashes; i++) {
bytes32 a = leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++];
bytes32 b = proofFlags[i]
? (leafPos < leavesLen ? leaves[leafPos++] : hashes[hashPos++])
: proof[proofPos++];
hashes[i] = _hashPair(a, b);
}
if (totalHashes > 0) {
if (proofPos != proofLen) {
revert MerkleProofInvalidMultiproof();
}
unchecked {
return hashes[totalHashes - 1];
}
} else if (leavesLen > 0) {
return leaves[0];
} else {
return proof[0];
}
}
/**
* @dev Sorts the pair (a, b) and hashes the result.
*/
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) {
return a < b ? _efficientHash(a, b) : _efficientHash(b, a);
}
/**
* @dev Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory.
*/
function _efficientHash(bytes32 a, bytes32 b) private pure returns (bytes32 value) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, a)
mstore(0x20, b)
value := keccak256(0x00, 0x40)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/MessageHashUtils.sol)
pragma solidity ^0.8.20;
import {Strings} from "../Strings.sol";
/**
* @dev Signature message hash utilities for producing digests to be consumed by {ECDSA} recovery or signing.
*
* The library provides methods for generating a hash of a message that conforms to the
* https://eips.ethereum.org/EIPS/eip-191[EIP 191] and https://eips.ethereum.org/EIPS/eip-712[EIP 712]
* specifications.
*/
library MessageHashUtils {
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing a bytes32 `messageHash` with
* `"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* NOTE: The `messageHash` parameter is intended to be the result of hashing a raw message with
* keccak256, although any bytes32 value can be safely used because the final digest will
* be re-hashed.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes32 messageHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, "\x19Ethereum Signed Message:\n32") // 32 is the bytes-length of messageHash
mstore(0x1c, messageHash) // 0x1c (28) is the length of the prefix
digest := keccak256(0x00, 0x3c) // 0x3c is the length of the prefix (0x1c) + messageHash (0x20)
}
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x45` (`personal_sign` messages).
*
* The digest is calculated by prefixing an arbitrary `message` with
* `"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the
* hash signed when using the https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`] JSON-RPC method.
*
* See {ECDSA-recover}.
*/
function toEthSignedMessageHash(bytes memory message) internal pure returns (bytes32) {
return
keccak256(bytes.concat("\x19Ethereum Signed Message:\n", bytes(Strings.toString(message.length)), message));
}
/**
* @dev Returns the keccak256 digest of an EIP-191 signed data with version
* `0x00` (data with intended validator).
*
* The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended
* `validator` address. Then hashing the result.
*
* See {ECDSA-recover}.
*/
function toDataWithIntendedValidatorHash(address validator, bytes memory data) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(hex"19_00", validator, data));
}
/**
* @dev Returns the keccak256 digest of an EIP-712 typed data (EIP-191 version `0x01`).
*
* The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with
* `\x19\x01` and hashing the result. It corresponds to the hash signed by the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`] JSON-RPC method as part of EIP-712.
*
* See {ECDSA-recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32 digest) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40)
mstore(ptr, hex"19_01")
mstore(add(ptr, 0x02), domainSeparator)
mstore(add(ptr, 0x22), structHash)
digest := keccak256(ptr, 0x42)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract Pausable is Context {
bool private _paused;
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
/**
* @dev The operation failed because the contract is paused.
*/
error EnforcedPause();
/**
* @dev The operation failed because the contract is not paused.
*/
error ExpectedPause();
/**
* @dev Initializes the contract in unpaused state.
*/
constructor() {
_paused = false;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
_requireNotPaused();
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
_requirePaused();
_;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Throws if the contract is paused.
*/
function _requireNotPaused() internal view virtual {
if (paused()) {
revert EnforcedPause();
}
}
/**
* @dev Throws if the contract is not paused.
*/
function _requirePaused() internal view virtual {
if (!paused()) {
revert ExpectedPause();
}
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ReentrancyGuard } from
"@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import { EIP712Upgradeable } from
"@openzeppelin-upgradeable/contracts/utils/cryptography/EIP712Upgradeable.sol";
import { ComptrollerManager } from "./helpers/ComptrollerManager.sol";
import { SafeTransferLib } from "@solady/utils/SafeTransferLib.sol";
import { IPearGmxFactoryV2 } from "./interfaces/IPearGmxFactoryV2.sol";
import { IPlatformLogic } from "./interfaces/IPlatformLogic.sol";
import { IChainLinkAggregator } from "./interfaces/IChainLinkAggregator.sol";
import { IRewardsClaimer } from "./interfaces/IRewardsClaimer.sol";
import { Errors } from "./libraries/Errors.sol";
/**
* @title PlatformLogic Contract
* @dev Implements fee structures, referral code logic, and interactions with
* factories and external contracts.
*
* The contract manages various types of fees (platform fees, manage position
* fees, mint fees) with percentage values based on a 10000-point scale, where
* 10000 represents 100%.
* It handles referral-related logic, including creation and management of
* referral codes, and tracks referred users. Fees are adjustable through admin
* functions.
*
* It includes mechanisms for calculating and applying fees, handling token
* transfers, and managing allowances for fee payments. The contract interacts
* with external Factory contracts,
* a Comptroller contract for platform settings, and Pear Treasury and Staking
* contracts for fee distribution.
*
* The contract also provides functionalities to set and retrieve numerous
* parameters related to fees, addresses of various components like treasury and
* staking contracts, and to manage factory addresses.
*
* @notice Manages fee structures within the platform.
* The fees are expressed as a fraction of 10000, representing 100%.
* For instance, a platform fee of 25 corresponds to 0.25% of the margin
* size.
* Additional fees, like referrer and referee fees, are percentages of
* the platform fee.
* A value of 500 for the referrer fee represents 5% of the platform
* fee.
* These fees are configurable through setter functions.
* @notice Keeps Referral Code Logic
*/
//TODO : Should be change this with V2 of PlatformLogic
contract PlatformLogic is
IPlatformLogic,
ComptrollerManager,
EIP712Upgradeable,
ReentrancyGuard
{
/// @inheritdoc IPlatformLogic
address public override backendVerifier;
// /// @inheritdoc IPlatformLogic
// bool public override testFeePlatformLogic;
/// @inheritdoc IPlatformLogic
uint256 public override platformFee;
/// @inheritdoc IPlatformLogic
uint256 public override mintFeeInUsdc;
/// @inheritdoc IPlatformLogic
uint256 public override referrerFee;
/// @inheritdoc IPlatformLogic
uint256 public override refereeDiscount;
/// @inheritdoc IPlatformLogic
uint256 public override treasuryFeeSplit;
/// @inheritdoc IPlatformLogic
uint256 public override gmxArbRewardsFeeSplit;
/// @inheritdoc IPlatformLogic
uint256 public override vertexArbRewardsFeeSplit;
/// @inheritdoc IPlatformLogic
uint256 public override symmArbRewardsFeeSplit;
/// @inheritdoc IPlatformLogic
address payable public override PearTreasury;
/// @inheritdoc IPlatformLogic
address payable public override PearStakingContract;
/// @notice switch to activate if Arb Rewards are running
bool public override isGmxArbRewardActive;
/// @notice switch to activate if Arb Rewards are running
bool public override isVertexArbRewardActive;
/// @notice switch to activate if Arb Rewards are running
bool public override isSymmArbRewardActive;
/// @notice store the Factory Addresses
mapping(address => bool) public factories;
/// @notice storing the referral codes and referral code owners
mapping(bytes32 => address) public referralCodes;
/// @notice mapping to store the Refferal codes to user's addresses(creator
/// of referral codes)
/// @notice address = referrer, bytes32 = referral code
mapping(address => bytes32) public referrers;
/// @notice mapping to store the Reffered users to referral codes(users
/// being referred)
mapping(address => bytes32) public referredUsers;
// address referrer => tokenAddress => amount
mapping(address => mapping(IERC20 => uint256)) private
pendingTokenWithdrawals;
/// @notice mapping to store the pending eth withdrawals user address to
/// amount
/// @dev this is used for referral logic
mapping(address => uint256) public pendingEthWithdrawals;
/// @notice nested mapping used to track users feeAmount when given user
/// address and orderKeys
mapping(address => mapping(address => uint256[2])) private refereeFeeAmounts;
/// @notice track totalFee amount paid by the user
mapping(address => uint256) public totalFeeAmountPaid;
/// @notice nested mapping used to track pending referrer feeAmount when
/// given user address and adapter address
// address referrer -> (address Gmx Adapter(holding both positions) ->
// uint256 feeAmount)
mapping(address => mapping(address => uint256)) public
pendingReferrerFeeAmounts;
uint256 constant TOTAL_BASIS_POINTS = 10_000;
/// @notice vertex platform hash used for arb rewards
bytes32 public constant vertexPlatform = keccak256("vertex");
/// @notice symm platform hash used for arb rewards
bytes32 public constant symmPlatform = keccak256("symm");
/// @notice gmx platform hash used for arb rewards
bytes32 public constant gmxPlatform = keccak256("gmx");
/**
* @notice Modifier to allow only factory addresses to execute a function.
* @dev Reverts if the caller is not a registered factory.
*/
modifier onlyFactory() {
if (factories[msg.sender] != true) {
revert Errors.PlatformLogic_NotFactory();
}
_;
}
/*
* @notice Modifier to restrict function execution to the positionNft as
defined in the comptroller.
* @dev Reverts if the caller is not the positionNft address set in the
comptroller contract.
*/
modifier onlyPositionNft() {
address positionNft = comptroller.getPositionNft();
if (msg.sender != positionNft) {
revert Errors.PlatformLogic_NotPositionNFT();
}
_;
}
/**
* @notice Modifier to restrict function execution to the admin of the
* comptroller.
* @dev Reverts if the caller is not the admin address set in the
* comptroller contract.
*/
modifier onlyAdmin() {
if (msg.sender != comptroller.admin()) {
revert Errors.PlatformLogic_NotComptrollerAdmin();
}
_;
}
/**
* @notice Modifier to ensure that a given address is not the zero address.
* @param _address The address to be checked.
* @dev Reverts if the provided address is the zero address.
*/
modifier notZeroAddress(address _address) {
if (_address == address(0x0)) {
revert Errors.PlatformLogic_GivenZeroAddress();
}
_;
}
modifier notExceedingOneHundredPercent(uint256 _bps) {
if (_bps > TOTAL_BASIS_POINTS) {
revert Errors.PlatformLogic_ExceedingBps();
}
_;
}
constructor() {
_disableInitializers();
}
/**
* @notice Constructor for the PlatformLogic contract.
* @param _factory Initial factory address to be registered in the contract.
* @param _PearTreasury Address of the Pear Treasury contract.
* @param _PearStakingContract Address of the Pear Staking contract.
* @param _BackendVerifier Address used to verify backend operations.
* @param SIGNING_DOMAIN Domain used for EIP712 signing.
* @param SIGNATURE_VERSION Version of the signature used for EIP712.
* @param _comptroller Address of the comptroller contract.
*/
function initialize(
address _factory,
address payable _PearTreasury,
address payable _PearStakingContract,
address _BackendVerifier,
string memory SIGNING_DOMAIN,
string memory SIGNATURE_VERSION,
address _comptroller
)
external
initializer
{
__EIP712_init(SIGNING_DOMAIN, SIGNATURE_VERSION);
factories[_factory] = true;
PearTreasury = _PearTreasury;
PearStakingContract = _PearStakingContract;
backendVerifier = _BackendVerifier;
_comptrollerInit(_comptroller);
/// reinitiaze predefined values
platformFee = 25;
/// @ndev setting mint fee to 1 USDC
mintFeeInUsdc = 1_000_000;
referrerFee = 500;
refereeDiscount = 1000;
treasuryFeeSplit = 10_000;
/// @dev sets all arb rewards fee splits to be 80% of the total fee
gmxArbRewardsFeeSplit = 8000;
vertexArbRewardsFeeSplit = 8000;
symmArbRewardsFeeSplit = 8000;
}
receive() external payable { }
/// @inheritdoc IPlatformLogic
function setBackendVerifierAddress(address _newBackendVerifier)
external
override
onlyAdmin
notZeroAddress(_newBackendVerifier)
{
emit BackendVerifierChanged(backendVerifier, _newBackendVerifier);
backendVerifier = _newBackendVerifier;
}
/// @inheritdoc IPlatformLogic
function setGmxArbRewardActive(bool _isArbRewardActive)
external
override
onlyAdmin
{
isGmxArbRewardActive = _isArbRewardActive;
}
/// @inheritdoc IPlatformLogic
function setVertexArbRewardActive(bool _isArbRewardActive)
external
override
onlyAdmin
{
isVertexArbRewardActive = _isArbRewardActive;
}
/// @inheritdoc IPlatformLogic
function setSymmArbRewardActive(bool _isArbRewardActive)
external
override
onlyAdmin
{
isSymmArbRewardActive = _isArbRewardActive;
}
/// @inheritdoc IPlatformLogic
function setGmxArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit)
external
override
onlyAdmin
{
emit ArbRewardsFeeSplitChanged(
gmxArbRewardsFeeSplit, _arbRewardsFeeSplit
);
gmxArbRewardsFeeSplit = _arbRewardsFeeSplit;
}
/// @inheritdoc IPlatformLogic
function setVertexArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit)
external
override
onlyAdmin
{
emit ArbRewardsFeeSplitChanged(
vertexArbRewardsFeeSplit, _arbRewardsFeeSplit
);
vertexArbRewardsFeeSplit = _arbRewardsFeeSplit;
}
/// @inheritdoc IPlatformLogic
function setSymmArbRewardsFeeSplit(uint256 _arbRewardsFeeSplit)
external
override
onlyAdmin
{
emit ArbRewardsFeeSplitChanged(
symmArbRewardsFeeSplit, _arbRewardsFeeSplit
);
symmArbRewardsFeeSplit = _arbRewardsFeeSplit;
}
/// @inheritdoc IPlatformLogic
function setRefereeDiscount(uint256 _refereeDiscount)
external
override
onlyAdmin
notExceedingOneHundredPercent(_refereeDiscount)
{
emit RefereeDiscountChanged(refereeDiscount, _refereeDiscount);
refereeDiscount = _refereeDiscount;
}
/// @inheritdoc IPlatformLogic
function setReferrerFee(uint256 _referrerFee)
external
override
onlyAdmin
notExceedingOneHundredPercent(_referrerFee)
{
emit ReferrerFeeChanged(referrerFee, _referrerFee);
referrerFee = _referrerFee;
}
/// @inheritdoc IPlatformLogic
function setPlatformFee(uint256 _platformFee)
external
override
onlyAdmin
notExceedingOneHundredPercent(_platformFee)
{
emit PlatformFeeChanged(platformFee, _platformFee);
platformFee = _platformFee;
}
/// @inheritdoc IPlatformLogic
function setMintPositionFee(uint256 _fee)
external
override
onlyAdmin
notExceedingOneHundredPercent(_fee)
{
emit MintFeeChanged(mintFeeInUsdc, _fee);
mintFeeInUsdc = _fee;
}
/// @inheritdoc IPlatformLogic
function setTreasuryFeeSplit(uint256 _treasuryFeeSplit)
external
override
onlyAdmin
notExceedingOneHundredPercent(_treasuryFeeSplit)
{
emit TreasuryFeeSplitChanged(treasuryFeeSplit, _treasuryFeeSplit);
treasuryFeeSplit = _treasuryFeeSplit;
}
/// @inheritdoc IPlatformLogic
function setPearTreasury(address payable _newTreasury)
external
override
onlyAdmin
{
emit PearTreasuryChanged(PearTreasury, _newTreasury);
PearTreasury = _newTreasury;
}
/// @inheritdoc IPlatformLogic
function setPearStakingContract(address payable _newStakingContract)
external
override
onlyAdmin
notZeroAddress(_newStakingContract)
{
emit PearStakingContractChanged(
PearStakingContract, _newStakingContract
);
PearStakingContract = _newStakingContract;
}
/// @inheritdoc IPlatformLogic
function setRefereeFeeAmount(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _feeAmount
)
public
override
onlyFactory
{
uint8 index = _isLong ? 0 : 1;
refereeFeeAmounts[_referee][_adapterAddress][index] = _feeAmount;
emit SetRefereeFeeAmount(_referee, _adapterAddress, _feeAmount, _isLong);
}
function setPendingReferrerFeeAmount(
address adapterAddress,
address referrer,
uint256 amount
)
internal
{
pendingReferrerFeeAmounts[adapterAddress][referrer] = amount;
emit SetPendingReferrerFeeAmount(adapterAddress, referrer, amount);
}
/// @inheritdoc IPlatformLogic
function setFactory(
address _factory,
bool _state
)
external
override
onlyAdmin
notZeroAddress(_factory)
{
factories[_factory] = _state;
emit FactorySet(_factory, _state);
}
/// @inheritdoc IPlatformLogic
function calculateFees(
uint256 _amount,
uint256 _bps
)
public
pure
override
returns (uint256)
{
/// @notice Check that the amount multiplied by the Basis Points is
/// greater than or equal to 10000
// This ensures that we're not running into the issue of values being
// rounded down to 0.
if ((_amount * _bps) < 10_000) {
revert Errors.PlatformLogic_WrongFeeAmount();
}
return (_amount * _bps) / 10_000;
}
function convertFeeToEth(uint256 feeAmount) public view returns (uint256) {
IChainLinkAggregator ethUsdAggregator =
IChainLinkAggregator(comptroller.getEthUsdAggregator());
(, int256 ethUsdPrice,,,) = ethUsdAggregator.latestRoundData(); // 8
// decimals
/// @dev feeInEth should be => feeAmount / ethUsdPrice
/// @dev but in actual (feeAmount * 1e6) / (ethUsdPrice * 1e8)
/// @dev feeInEth(with 18 decimal) => ((feeAmount * 100) / ethUsdPrice )
/// * 1e18
uint256 feeInEth = (feeAmount * 1e20) / uint256(ethUsdPrice);
return feeInEth;
}
function calculateReferrerFeeFromPearFee(uint256 feeAmount)
internal
view
returns (uint256)
{
return (feeAmount * referrerFee) / (10_000 - referrerFee);
}
/// @inheritdoc IPlatformLogic
function handleTokenAmountWhenPositionHasFailed(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _feeAmount
)
external
override
onlyFactory
{
uint8 index = _isLong ? 0 : 1;
if (refereeFeeAmounts[_referee][_adapterAddress][index] == 0) {
revert Errors.PlatformLogic_FeeAmountCannotBeNull();
}
address referrer = checkReferredUser(_referee);
if (referrer != address(0)) {
uint256 amount = calculateReferrerFeeFromPearFee(_feeAmount);
if (pendingReferrerFeeAmounts[_adapterAddress][referrer] < amount) {
amount = pendingReferrerFeeAmounts[_adapterAddress][referrer];
}
_feeAmount += amount;
setPendingReferrerFeeAmount(
_adapterAddress,
referrer,
pendingReferrerFeeAmounts[_adapterAddress][referrer] - amount
);
}
setRefereeFeeAmount(_referee, _adapterAddress, _isLong, 0);
SafeTransferLib.safeTransferETH(_referee, _feeAmount);
}
/// @inheritdoc IPlatformLogic
function addTokenFeesForWithdrawal(
address _referrer,
uint256 _amount,
IERC20 _token
)
public
override
notZeroAddress(_referrer)
onlyFactory
{
// add a withdrawTokenFee function
pendingTokenWithdrawals[_referrer][_token] += _amount;
emit PendingTokenWithdrawal(_referrer, _amount);
}
/// @inheritdoc IPlatformLogic
function withdrawTokenFees(IERC20 _token) public override nonReentrant {
uint256 _balance = pendingTokenWithdrawals[msg.sender][_token];
if (_balance == 0) revert Errors.PlatformLogic_NotEnoughBalance();
pendingTokenWithdrawals[msg.sender][_token] = 0;
SafeTransferLib.safeTransfer(address(_token), msg.sender, _balance);
emit TokenWithdrawal(msg.sender, _balance);
}
/// @inheritdoc IPlatformLogic
function withdrawEthFees() public override nonReentrant {
uint256 _balance = pendingEthWithdrawals[msg.sender];
if (_balance == 0) revert Errors.PlatformLogic_NotEnoughBalance();
pendingEthWithdrawals[msg.sender] = 0;
SafeTransferLib.safeTransferETH(msg.sender, _balance);
emit EthWithdrawal(msg.sender, _balance);
}
/// @inheritdoc IPlatformLogic
/// @notice called only from GMX's side - for the arb rewards fee split
function splitBetweenStakersAndTreasuryEth(
address _referee,
address _adapterAddress,
bool _isLong,
uint256 _amount
)
external
override
onlyFactory
{
uint8 index = _isLong ? 0 : 1;
address referrer = checkReferredUser(_referee);
if (referrer != address(0)) {
uint256 amount = calculateReferrerFeeFromPearFee(_amount);
if (pendingReferrerFeeAmounts[_adapterAddress][referrer] < amount) {
amount = pendingReferrerFeeAmounts[_adapterAddress][referrer];
}
_addEthFeesForWithdrawal(referrer, amount);
setPendingReferrerFeeAmount(
_adapterAddress,
referrer,
pendingReferrerFeeAmounts[_adapterAddress][referrer] - amount
);
emit Referred(referrer, _referee, amount);
}
// calculate % to be send to treasury
uint256 _amountToBeSendToTreasury =
calculateFees(_amount, treasuryFeeSplit);
/// it won't affect the logic but better to reset
// setRefereeFeeAmount(_referee, _adapterAddress, _isLong, 0);
totalFeeAmountPaid[_referee] +=
refereeFeeAmounts[_referee][_adapterAddress][index];
// send the amount to the Treasurys
SafeTransferLib.safeTransferETH(PearTreasury, _amountToBeSendToTreasury);
emit FeesPaidToPearTreasury(PearTreasury, _amountToBeSendToTreasury);
/// @dev why does this give 8 and the calculatefees approach 9 when
/// given 1000 as token amount?
uint256 _amountToBeSendToStakers = _amount - _amountToBeSendToTreasury;
if (_amountToBeSendToStakers > 0) {
SafeTransferLib.safeTransferETH(
PearStakingContract, _amountToBeSendToStakers
);
emit FeesPaidToStakingContract(
PearStakingContract, _amountToBeSendToStakers
);
}
if (isGmxArbRewardActive) {
// calculate % of arb fees as rebates
uint256 arbFeeRewards =
calculateFees(_amount, gmxArbRewardsFeeSplit);
// add the fee to the rewards claimer
IRewardsClaimer(comptroller.getArbRewardsClaimer())
.addPendingUserRewards(_referee, arbFeeRewards, gmxPlatform);
emit PlatformLogic_AddedRewards(_referee, arbFeeRewards);
}
}
/// @dev funciton that adds the ETH fees available for withdrawal from the
/// referrer
function _addEthFeesForWithdrawal(
address _referrer,
uint256 _amount
)
internal
notZeroAddress(_referrer)
{
// some checks, like cannot be address 0 - can be made into a modifer
// and appied to the applyPlatformFee too
// add fees to pendingWithdrawal mapping referrer address to uint256
// amount
pendingEthWithdrawals[_referrer] += _amount;
// emit event
emit PendingEthWithdrawal(_referrer, _amount);
}
function getplatformFeeOfOrder(
address referee,
uint256 grossAmount
)
public
view
override
returns (uint256)
{
uint256 _feeAmountInUSD = calculateFees(grossAmount, platformFee);
uint256 _feeAmount = convertFeeToEth(_feeAmountInUSD);
if (referredUsers[referee] != 0) {
uint256 _refereeDiscount =
calculateFees(_feeAmount, refereeDiscount);
_feeAmount -= _refereeDiscount;
}
return _feeAmount;
}
/// @notice user needs to give allowance to the contract first so it can
/// send the tokens,
/// allowance to Factory, because it will call this
/// @param adapter address of the adapter initiating the order
/// @param _referee the user that is referred, usually msg.sender
/// @param _grossAmount the total amount of the tx
/// @param _factory the factory address
function _applyPlatformFeeETHGmx(
address adapter,
address _referee,
uint256 _grossAmount,
address _factory
)
internal
returns (uint256, uint256)
{
// calculates the fees with the given gross amount
// calculate using the platformFee used for opening positions
uint256 _feeAmountInUsd = calculateFees(_grossAmount, platformFee);
uint256 _feeAmount = convertFeeToEth(_feeAmountInUsd); // convert to eth
// check referree (if user is referred)
/// -> if true add 10% discount -> continue to the next check, if not
/// skip to the end
if (referredUsers[_referee] != 0) {
// calculate the referree discount %
// for testing the refereeDiscount is set to 10 bps
uint256 _refereeDiscount =
calculateFees(_feeAmount, refereeDiscount);
// remove the discounted % from the referee fees
// that have to be paid at the end
_feeAmount -= _refereeDiscount;
// calculate the referrer discount
// for testing the referralFee is set to 5 bps
uint256 _referrerWithdrawal = calculateFees(_feeAmount, referrerFee);
// User -ETH-> Factory -> Platform --calculatefee-> ask to factory
// to transfer to ETH
bool success =
IPearGmxFactoryV2(_factory).feeTransferPlatformLogic(_feeAmount);
if (!success) {
revert Errors.PlatformLogic_NotEnoughBalance();
}
setPendingReferrerFeeAmount(
adapter, checkReferredUser(_referee), _referrerWithdrawal
);
uint256 _grossAmountAfterFee = _grossAmount - _feeAmountInUsd;
emit FeesPaid(_referee, _feeAmount, _grossAmountAfterFee);
// remove the discounted % referee withdrawal
_feeAmount -= _referrerWithdrawal;
/// @dev returns the amount that needs to be split afterwards or
/// returned to user
return (_feeAmount, _referrerWithdrawal);
} else {
bool success =
IPearGmxFactoryV2(_factory).feeTransferPlatformLogic(_feeAmount);
if (!success) {
revert Errors.PlatformLogic_NotEnoughBalance();
}
uint256 _grossAmountAfterFee = _grossAmount - _feeAmountInUsd;
emit FeesPaid(_referee, _feeAmount, _grossAmountAfterFee);
/// @dev returns the amount that needs to be split afterwards or
/// returned to user
return (_feeAmount, 0);
}
}
/// @notice user needs to give allowance to the contract first so it can
/// send the tokens,
/// allowance to Factory, because it will call this
/// @param _referee the user that is referred, usually msg.sender
function _applyMintFeeEthGmx(address _referee) internal {
uint256 _feeAmount = convertFeeToEth(mintFeeInUsdc);
if (msg.value != _feeAmount) {
revert Errors.PlatformLogic_WrongValueSent(msg.value, _feeAmount);
}
emit FeesPaid(_referee, _feeAmount, 0);
// // calculate % to be send to treasury
uint256 _amountToBeSendToTreasury =
calculateFees(_feeAmount, treasuryFeeSplit);
// send the amount to the Treasury
(bool _successTreasury,) =
PearTreasury.call{ value: _amountToBeSendToTreasury }("");
if (!_successTreasury) {
revert Errors.PlatformLogic_TransactionFailed();
}
// /// @dev why does this give 8 and the calculatefees approach 9 when
// /// given 1000 as token amount?
uint256 _amountToBeSendToStakers =
_feeAmount - _amountToBeSendToTreasury;
/// @dev consider using solady's safeTransferETH
if (_amountToBeSendToStakers > 0) {
(bool _successStakers,) =
PearStakingContract.call{ value: _amountToBeSendToStakers }("");
if (!_successStakers) {
revert Errors.PlatformLogic_TransactionFailed();
}
emit FeesPaidToStakingContract(
PearStakingContract, _amountToBeSendToStakers
);
}
emit FeesPaidToPearTreasury(PearTreasury, _amountToBeSendToTreasury);
}
/// @param _referee the user that is referred, usually msg.sender
function _applyPlatformFeeEthVertex(
address _referee,
uint256 _feeAmountAfterDiscountAndWithdrawal,
uint256 _feeAmountAfterDiscount,
uint256 _referrerWithdrawal,
bool _isReferralProgramApplied
)
internal
{
if (
_feeAmountAfterDiscountAndWithdrawal
!= _feeAmountAfterDiscount - _referrerWithdrawal
) {
revert Errors.PlatformLogic_WrongFeeAmount();
}
/// @dev maybe add this check:
// if (msg.value != _feeAmountAfterDiscount)
/// @dev used to prevent going into the function further and spending
/// more gas on calculations
if (comptroller.getPlatformLogic() != address(this)) {
revert Errors.PlatformLogic_AddressSetInComptrollerIsNotThisOne();
}
// check referree (if user is referred) -> if true add 10% discount ->
// continue to the next check, if not skip to the end
if (_isReferralProgramApplied) {
_addEthFeesForWithdrawal(
checkReferredUser(_referee), _referrerWithdrawal
);
// calculate % to be send to treasury
uint256 _amountToBeSendToTreasury = calculateFees(
_feeAmountAfterDiscountAndWithdrawal, treasuryFeeSplit
);
// send the amount to the Treasury
(bool _successTreasury,) =
PearTreasury.call{ value: _amountToBeSendToTreasury }("");
if (!_successTreasury) {
revert Errors.PlatformLogic_TransactionFailed();
}
uint256 _amountToBeSendToStakers =
_feeAmountAfterDiscountAndWithdrawal - _amountToBeSendToTreasury;
/// @dev consider using solady's safeTransferETH
if (_amountToBeSendToStakers > 0) {
(bool _successStakers,) = PearStakingContract.call{
value: _amountToBeSendToStakers
}("");
if (!_successStakers) {
revert Errors.PlatformLogic_TransactionFailed();
}
emit FeesPaidToStakingContract(
PearStakingContract, _amountToBeSendToStakers
);
}
if (isVertexArbRewardActive) {
// calculate % of arb fees as rebates
uint256 arbFeeRewards = calculateFees(
_feeAmountAfterDiscountAndWithdrawal,
vertexArbRewardsFeeSplit
);
// add the fee to the rewards claimer
IRewardsClaimer(comptroller.getArbRewardsClaimer())
.addPendingUserRewards(_referee, arbFeeRewards, vertexPlatform);
emit PlatformLogic_AddedRewards(_referee, arbFeeRewards);
}
emit FeesPaidToPearTreasury(PearTreasury, _amountToBeSendToTreasury);
} else {
if (msg.value != _feeAmountAfterDiscountAndWithdrawal) {
revert Errors.PlatformLogic_WrongValueSent(
msg.value, _feeAmountAfterDiscountAndWithdrawal
);
}
// calculate % to be send to treasury
uint256 _amountToBeSendToTreasury = calculateFees(
_feeAmountAfterDiscountAndWithdrawal, treasuryFeeSplit
);
// send the amount to the Treasury
(bool _successTreasury,) =
PearTreasury.call{ value: _amountToBeSendToTreasury }("");
if (!_successTreasury) {
revert Errors.PlatformLogic_TransactionFailed();
}
uint256 _amountToBeSendToStakers =
_feeAmountAfterDiscountAndWithdrawal - _amountToBeSendToTreasury;
if (_amountToBeSendToStakers > 0) {
(bool _successStakers,) = PearStakingContract.call{
value: _amountToBeSendToStakers
}("");
if (!_successStakers) {
revert Errors.PlatformLogic_TransactionFailed();
}
emit FeesPaidToStakingContract(
PearStakingContract, _amountToBeSendToStakers
);
}
if (isVertexArbRewardActive) {
// calculate % of arb fees as rebates
uint256 arbFeeRewards = calculateFees(
_feeAmountAfterDiscountAndWithdrawal,
vertexArbRewardsFeeSplit
);
// add the fee to the rewards claimer
IRewardsClaimer(comptroller.getArbRewardsClaimer())
.addPendingUserRewards(_referee, arbFeeRewards, vertexPlatform);
emit PlatformLogic_AddedRewards(_referee, arbFeeRewards);
}
emit FeesPaidToPearTreasury(PearTreasury, _amountToBeSendToTreasury);
}
}
/// @inheritdoc IPlatformLogic
function applyPlatformFeeETHGmx(
address adapter,
address _referee,
uint256 _grossAmount,
address _factory
)
external
override
onlyFactory
nonReentrant
returns (uint256 feeAmountToBeSplit, uint256 referrerWithdrawal)
{
return
_applyPlatformFeeETHGmx(adapter, _referee, _grossAmount, _factory);
}
/// @inheritdoc IPlatformLogic
function applyPlatformFeeEthVertex(
address _referee,
uint256 _feeAmountAfterDiscountAndWithdrawal,
uint256 _feeAmountAfterDiscount,
uint256 _referrerWithdrawal,
bool _isReferralProgramApplied
)
external
payable
override
onlyFactory
nonReentrant
{
_applyPlatformFeeEthVertex(
_referee,
_feeAmountAfterDiscountAndWithdrawal,
_feeAmountAfterDiscount,
_referrerWithdrawal,
_isReferralProgramApplied
);
}
/// @inheritdoc IPlatformLogic
function applyMintFeeEthGmx(address _referee)
external
payable
override
onlyPositionNft
nonReentrant
{
_applyMintFeeEthGmx(_referee);
}
/// @inheritdoc IPlatformLogic
function createReferralCode(ReferralCode calldata referrerCode)
external
override
returns (bool)
{
/// @dev allows only msg.sender to create a referral code with his
/// address
if (referrerCode.referrer != msg.sender) {
revert Errors.PlatformLogic_CodeCreatorIsNotMsgSender();
}
/// @dev ensure that the referral code is not used yet
// checks that address can be only one
if (
referrers[referrerCode.referrer] != 0
|| referralCodes[referrerCode.referralCode] != address(0)
) {
revert Errors.PlatformLogic_Unavailable();
}
// // Get the signer's address from the provided details
address signer = _verify(referrerCode);
if (signer != backendVerifier) {
revert Errors.PlatformLogic_InvalidSigner();
}
// write the EOA's referral code
referrers[referrerCode.referrer] = referrerCode.referralCode;
// write the referral code
referralCodes[referrerCode.referralCode] = referrerCode.referrer;
emit ReferralCodeAdded(referrerCode.referrer, referrerCode.referralCode);
return true;
}
/// @inheritdoc IPlatformLogic
function addReferee(
bytes32 _referralCode,
address _referee
)
external
override
notZeroAddress(referralCodes[_referralCode])
returns (bool)
{
/// @dev allows only msg.sender to assign a referral code to himself
if (_referee != msg.sender) {
revert Errors.PlatformLogic_RefereeNotMsgSender();
}
/// @dev ensure that the referee has not assigned code is not used yet
// check to ensure the code is valid / exists
// check that user is not adding himself as referee
if (
referredUsers[_referee] != 0 || referrers[_referee] == _referralCode
) {
revert Errors.PlatformLogic_Unavailable();
}
// save referee and code relation
referredUsers[_referee] = _referralCode;
/// @dev add the referrer too
// index the event
emit RefereeAdded(_referee, _referralCode);
return true;
}
/// @inheritdoc IPlatformLogic
function addRefereeFromFactory(
bytes32 _referralCode,
address _referee
)
external
override
onlyFactory
notZeroAddress(referralCodes[_referralCode])
returns (bool)
{
/// @dev ensure that the referee has not assigned code is not used yet
// check to ensure the code is valid / exists
// check that user is not adding himself as referee
if (
referredUsers[_referee] != 0 || referrers[_referee] == _referralCode
) revert Errors.PlatformLogic_Unavailable();
// save referee and code relation
referredUsers[_referee] = _referralCode;
/// @dev add the referrer too
// index the event
emit RefereeAdded(_referee, _referralCode);
return true;
}
/// @inheritdoc IPlatformLogic
function editReferralCode(
address _referrer,
bytes32 _referralCode
)
external
override
onlyAdmin
{
// write the address's referral code
referrers[_referrer] = _referralCode;
// write the referral code
referralCodes[_referralCode] = _referrer;
// emit event for indexing
emit ReferralCodeEdited(_referrer, _referralCode, msg.sender);
}
/// @inheritdoc IPlatformLogic
function editReferredUsers(
address _referee,
bytes32 _referralCode
)
external
override
onlyAdmin
{
// write the address's referral code
referredUsers[_referee] = _referralCode;
// emit event for indexing
emit RefereeEdited(_referee, _referralCode, msg.sender);
}
/// @dev move elsewhere to public functions, add onlyFactory call?
function calculatePlatformFeeEthVertex(
address _referee,
uint256 _grossAmount
)
public
view
override
returns (
uint256 _feeAmountAfterDiscountAndWithdrawal,
uint256 _feeAmountAfterDiscount,
uint256 _referrerWithdrawal,
bool _isReferralProgramApplied
)
{
uint256 _feeAmount = calculateFees(_grossAmount, platformFee);
// check referree (if user is referred) -> if true add 10% discount ->
// continue to the next check, if not skip to the end
if (referredUsers[_referee] != 0) {
// calculate the referree discount %
// for testing the refereeDiscount is set to 10 bps
uint256 _refereeDiscount =
calculateFees(_feeAmount, refereeDiscount);
// calculate the referrer discount
// for testing the referralFee is set to 5 bps
_referrerWithdrawal = calculateFees(_feeAmount, referrerFee);
// remove the discounted % from the referee fees and referee
// withdrawal
// that have to be paid at the end
_feeAmountAfterDiscount = _feeAmount - _refereeDiscount;
// here usually the platformLogic would add ETH fees for withdrawal
// remove the discounted % referee withdrawal
_feeAmountAfterDiscountAndWithdrawal =
_feeAmountAfterDiscount - _referrerWithdrawal;
return (
_feeAmountAfterDiscountAndWithdrawal,
_feeAmountAfterDiscount,
_referrerWithdrawal,
true
);
}
return (_feeAmount, _feeAmount, 0, false);
}
/// @notice Returns a hash of the given ReferralCode, prepared using EIP712
/// typed data hashing rules.
/// @param referrerCode An referralCode to hash.
function _hash(ReferralCode calldata referrerCode)
internal
view
returns (bytes32)
{
return _hashTypedDataV4(
keccak256(
abi.encode(
keccak256(
"ReferralCode(address referrer, bytes32 referralCode)"
),
referrerCode.referrer,
referrerCode.referralCode
)
)
);
}
/// @notice Returns the chain id of the current blockchain.
/// @dev This is used to workaround an issue with ganache returning
/// different values from the on-chain chainid() function and
/// the eth_chainId RPC method.
function getChainID() external view override returns (uint256) {
uint256 id;
assembly {
id := chainid()
}
return id;
}
/// @notice Verifies the signature for a given ReferralCode, returning the
/// address of the signer.
/// @dev Will revert if the signature is invalid. Does not verify that the
/// referrer is authorized to create a code.
/// @param referrerCode An ReferralCode describing a non created referrral
/// code.
/// @dev currently public for testing, switch to internal/private before
/// deploy
function _verify(ReferralCode calldata referrerCode)
internal
view
returns (address)
{
bytes32 digest = _hash(referrerCode);
return ECDSA.recover(digest, referrerCode.signature);
}
/// @inheritdoc IPlatformLogic
function viewReferralCodeOwner(bytes32 _referralCode)
external
view
override
returns (address codeOwner)
{
return referralCodes[_referralCode];
}
/// @inheritdoc IPlatformLogic
function viewReferrersCode(address _referrer)
external
view
override
returns (bytes32 code)
{
return referrers[_referrer];
}
/// @inheritdoc IPlatformLogic
function viewReferredUser(address _referredUser)
external
view
override
returns (bytes32 code)
{
return referredUsers[_referredUser];
}
/// @inheritdoc IPlatformLogic
function checkReferredUser(address _referredUser)
public
view
override
returns (address referrer)
{
bytes32 _code = referredUsers[_referredUser];
// returns the referral code owner
return referralCodes[_code];
}
/// @inheritdoc IPlatformLogic
function checkFactory(address _factory)
external
view
override
returns (bool)
{
return factories[_factory];
}
/// @inheritdoc IPlatformLogic
function viewPlatformFee() external view override returns (uint256) {
return platformFee;
}
/// @inheritdoc IPlatformLogic
function viewRefereeDiscount() external view override returns (uint256) {
return refereeDiscount;
}
/// @inheritdoc IPlatformLogic
function viewReferrerFee() external view override returns (uint256) {
return referrerFee;
}
/// @inheritdoc IPlatformLogic
function viewRefereeFeeAmount(
address _referee,
address _adapterAddress,
bool _isLong
)
public
view
override
returns (uint256)
{
return refereeFeeAmounts[_referee][_adapterAddress][_isLong ? 0 : 1];
}
/// @inheritdoc IPlatformLogic
function checkPendingTokenWithdrawals(
address _referrer,
IERC20 _token
)
external
view
override
returns (uint256)
{
return pendingTokenWithdrawals[_referrer][_token];
}
}
// 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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { PlatformLogic } from "./../PlatformLogic.sol";
import { Comptroller } from "./../Comptroller.sol";
import { ERC1967Factory } from "@solady/utils/ERC1967Factory.sol";
import { Pausable } from
"@openzeppelin-upgradeable/lib/openzeppelin-contracts/contracts/utils/Pausable.sol";
import { MerkleProof } from
"@openzeppelin-upgradeable/lib/openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";
import { ComptrollerManager } from "./../helpers/ComptrollerManager.sol";
import { IChainLinkAggregator } from "./../interfaces/IChainLinkAggregator.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IRewardsClaimer } from "./../interfaces/IRewardsClaimer.sol";
import { ReentrancyGuard } from
"@openzeppelin-upgradeable/lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
import { Errors } from "./../libraries/Errors.sol";
import { ComptrollerManager } from "./../helpers/ComptrollerManager.sol";
contract RewardsClaimer is
Pausable,
ReentrancyGuard,
IRewardsClaimer,
ComptrollerManager
{
/// @notice vertex platform hash
bytes32 public constant vertexPlatform = keccak256("vertex");
/// @notice symm platform hash
bytes32 public constant symmPlatform = keccak256("symm");
/// @notice gmx platform hash
bytes32 public constant gmxPlatform = keccak256("gmx");
/// @notice token to be rewarded to users
address public rewardToken;
/// @notice address of the ARB/USD aggregator
address public arbUsdAggregator;
/// @notice decimals of the reward token
uint128 public rewardTokenDecimals = 1_000_000_000_000_000_000; // asuuming
// ARB token - 1e18
/// @notice maximum number of ARB tokens per wallet
uint128 public maxClaimPerWallet;
// / @notice total rewards to be claimed in tokens
// uint256 public totalGmxRewardsAdded;
// /// @notice total rewards to be claimed in tokens
// uint256 public totalVertexRewardsAdded;
// /// @notice total rewards to be claimed in tokens
// uint256 public totalSymmRewardsAdded;
/// @notice total rewards to be spend by the platform for all 3 of the
/// platforms
/// @dev this mapping should be updated when owner deposits more reward
/// tokens with the split he defines
// and should be subtracted when the user claims the rewards
// also owner should be able to change it at any time, there should be
// checks to see if
// the max arb reward being spent is not above the amount
/// @notice should be used as a cap to how much tokens can be spent for each
/// platform
mapping(bytes32 platformHashed => uint256 maxArbRewardsSpend) public
maxArbRewardsToBeSpend;
/// @notice storing the block timestamp to user for the rewards
// used to prevent claiming rewards in the same block
/// @inheritdoc IRewardsClaimer
mapping(address => uint256) public lastRewardBlock;
/// @notice mapping of the rewards to be claimed for user
// mapping storing the rewards that have been added to the user for all
// platforms
/// @dev use the public getter
mapping(
address user => mapping(bytes32 platformHashed => uint256 rewardAmount)
) private userPendingRewards;
/// @notice mapping of the rewards to be claimed by users
/// @notice keeps track of addedRewards, that are still unclaimed
/// @inheritdoc IRewardsClaimer
// mapping storing the rewards that have been added to the user for all
// platforms
/// @notice takes in hashed value of platform
mapping(bytes32 platform => uint256 rewardAmount) public
totalUnclaimedRewards;
/// @notice mapping of the rewards that have been claimed
/// @inheritdoc IRewardsClaimer
// mapping storing the rewards that have been claimed by the user across all
// platforms
/// @notice not storing the rewards claimed by the user for each platform,
/// since max claim limit is per wallet
// is across all platforms
mapping(address user => uint256 rewardAmount) public
userClaimedRewardsAcrossAllPlatforms;
mapping(bytes32 platform => bool whitelisted) public whitelistedPlatforms;
/// @notice modifier to check if the caller is the owner
modifier onlyOwner() {
if (msg.sender != comptroller.admin()) {
revert Errors.RewardsClaimer_NotOwner();
}
_;
}
/// @notice modifier to check if the caller is the platform logic
modifier onlyPlatformLogic() {
if (msg.sender != comptroller.getPlatformLogic()) {
revert Errors.RewardsClaimer_NotPlatformLogic();
}
_;
}
/// @notice modifier to check if the platform is whitelisted
modifier onlyWhitelistedPlatform(bytes32 _platform) {
if (!whitelistedPlatforms[_platform]) {
revert Errors.RewardsClaimer_NotWhitelistedPlatform();
}
_;
}
/**
*
* @param _comptroller address of the comptroller contract
* @param _rewardToken address of the reward token
* @param _arbUsdAggregator address of the ARB/USD aggregator
* @param _maxClaimPerWallet maximum number of ARB tokens per wallet
*/
constructor(
address _comptroller,
address _rewardToken,
address _arbUsdAggregator,
uint128 _maxClaimPerWallet
) {
_comptrollerInit(_comptroller);
rewardToken = _rewardToken;
arbUsdAggregator = _arbUsdAggregator;
maxClaimPerWallet = _maxClaimPerWallet;
whitelistedPlatforms[vertexPlatform] = true;
whitelistedPlatforms[symmPlatform] = true;
whitelistedPlatforms[gmxPlatform] = true;
}
// function that platoformLogic writes to as a mapping, that stores the
// rewards
/// @dev assuming the feeAmount Calcualtion is done in the platformLogic via
/// the calculateFees function
/// @inheritdoc IRewardsClaimer
function addPendingUserRewards(
address _user,
uint256 _feeAmountInEth,
bytes32 _platform
)
external
override
onlyPlatformLogic
whenNotPaused
nonReentrant
onlyWhitelistedPlatform(_platform)
returns (uint256 arbAmount)
{
/// @dev checks if there are no more arb rewards to be spent, returns 0
/// if there aren't
if (maxArbRewardsToBeSpend[_platform] == 0) {
emit NoRewardsAddedDueToLimit(_user, _platform);
return 0;
}
uint256 _arbAmount = calculateArbAmount(_feeAmountInEth);
/// @dev if the arbAmount is greater than the max rewards to be spent,
/// then the arbAmount is the max rewards to be spent - users takes the
/// leftover arb amount
if (maxArbRewardsToBeSpend[_platform] < _arbAmount) {
_arbAmount = maxArbRewardsToBeSpend[_platform];
}
/// @dev once the arbAmount is allocated to the user ( as pending
/// withdrawal), we are subtracting it from the total rewards to be
/// spent
maxArbRewardsToBeSpend[_platform] -= _arbAmount;
userPendingRewards[_user][_platform] += _arbAmount;
totalUnclaimedRewards[_platform] += _arbAmount;
/// @dev storing the block timestamp to user for the rewards
lastRewardBlock[_user] = block.number;
emit AddedRewards(_user, _platform, _arbAmount);
return _arbAmount;
}
// function to claim rewards
/// @notice function to claim rewards
// withdrawing the rewards as a user
/// @inheritdoc IRewardsClaimer
function claimRewards(bytes32 _platform)
external
override
whenNotPaused
nonReentrant
onlyWhitelistedPlatform(_platform)
{
/// @dev checking if the user tries to claim rewards in the same block
if (lastRewardBlock[msg.sender] == block.number) {
revert Errors.RewardsClaimer_CannotClaimRewardsInTheSameBlock();
}
uint256 _pendingRewards = userPendingRewards[msg.sender][_platform];
uint256 _claimedRewards =
userClaimedRewardsAcrossAllPlatforms[msg.sender];
uint256 _totalClaimed = _pendingRewards + _claimedRewards;
// if the total claimed rewards is greater than the max claim per
// wallet, then the pending rewards is the max claim per wallet minus
// the claimed rewards - allowing users to claim the max claim per
// wallet available
if (_totalClaimed > maxClaimPerWallet) {
_pendingRewards = maxClaimPerWallet - _claimedRewards;
}
// if the pending rewards is greater than 0, then the user can claim
if (_pendingRewards > 0) {
userClaimedRewardsAcrossAllPlatforms[msg.sender] += _pendingRewards;
userPendingRewards[msg.sender][_platform] = 0;
totalUnclaimedRewards[_platform] -= _pendingRewards;
maxArbRewardsToBeSpend[_platform] -= _pendingRewards;
emit ClaimedRewards(msg.sender, _platform, _pendingRewards);
IERC20(rewardToken).transfer(msg.sender, _pendingRewards);
} else {
revert
Errors
.RewardsClaimer_UserHasNoRewardsToClaimOrHasExceededClaimingAmount();
}
}
/// @inheritdoc IRewardsClaimer
function checkRewards(
address _user,
bytes32 _platform
)
external
view
override
returns (uint256)
{
uint256 _pendingRewards = userPendingRewards[_user][_platform];
uint256 _claimedRewards = userClaimedRewardsAcrossAllPlatforms[_user];
uint256 _totalClaimed = _pendingRewards + _claimedRewards;
if (_totalClaimed > maxClaimPerWallet) {
_pendingRewards = maxClaimPerWallet - _claimedRewards;
}
return _pendingRewards;
}
/// @notice function to view ARB left in the contract
/// @inheritdoc IRewardsClaimer
function rewardTokensLeft() external view override returns (uint256) {
uint256 _balanceLeft = IERC20(rewardToken).balanceOf(address(this));
return _balanceLeft;
}
/**
* ADMIN FUNCTIONS
*/
/// @dev admins needs to call approve beforehand
/// @inheritdoc IRewardsClaimer
function depositRewardTokens(
uint256 _amountForIsolated,
uint256 _amountForCross,
uint256 _amountForIntent
)
external
onlyOwner
{
uint256 _totalAmount =
_amountForIsolated + _amountForCross + _amountForIntent;
maxArbRewardsToBeSpend[gmxPlatform] += _amountForIsolated;
maxArbRewardsToBeSpend[vertexPlatform] += _amountForCross;
maxArbRewardsToBeSpend[symmPlatform] += _amountForIntent;
IERC20(rewardToken).transferFrom(
msg.sender, address(this), _totalAmount
);
emit RewardTokensDeposited(
_totalAmount, _amountForIsolated, _amountForCross, _amountForIntent
);
}
/// @inheritdoc IRewardsClaimer
function editMaxArbRewardsToBeSpend(
bytes32 _platform,
uint256 _amount
)
external
onlyOwner
{
maxArbRewardsToBeSpend[_platform] = _amount;
}
/// @notice function to set arbUsdAggregator
/// @inheritdoc IRewardsClaimer
function setArbUsdAggregator(address _arbUsdAggregator)
external
override
onlyOwner
{
emit ArbUsdAggregatorChanged(arbUsdAggregator, _arbUsdAggregator);
arbUsdAggregator = _arbUsdAggregator;
}
/// @inheritdoc IRewardsClaimer
function setMaxClaimPerWallet(uint128 _maxClaimPerWallet)
external
override
onlyOwner
{
maxClaimPerWallet = _maxClaimPerWallet;
}
/// @inheritdoc IRewardsClaimer
function editWhitelistedPlatforms(
bytes32 _platform,
bool _whitelisted
)
external
onlyOwner
{
whitelistedPlatforms[_platform] = _whitelisted;
}
/// @inheritdoc IRewardsClaimer
function pause() external override onlyOwner {
_pause();
}
/// @inheritdoc IRewardsClaimer
function unPause() external override onlyOwner {
_unpause();
}
/// @inheritdoc IRewardsClaimer
function setRewardTokenDecimals(uint128 _rewardTokenDecimals)
external
override
onlyOwner
{
rewardTokenDecimals = _rewardTokenDecimals;
}
/// @inheritdoc IRewardsClaimer
function setRewardToken(address _rewardToken) external override onlyOwner {
rewardToken = _rewardToken;
}
/// @inheritdoc IRewardsClaimer
function withdrawTokens(
address _to,
uint256 _amount
)
external
override
onlyOwner
{
if (_to == address(0x0)) {
revert Errors.RewardsClaimer_CannotSendTo0Address();
}
IERC20(rewardToken).transfer(_to, _amount);
}
/// @inheritdoc IRewardsClaimer
function withdrawNonRewardTokens(
address _to,
uint256 _amount,
address _token
)
external
override
onlyOwner
{
if (_to == address(0x0)) {
revert Errors.RewardsClaimer_CannotSendTo0Address();
}
IERC20(_token).transfer(_to, _amount);
}
/**
* _ethAmountInWei is the amount of ETH in wei.
* ethUsdPrice is the ETH to USD conversion rate with 8 decimal places.
* arbUsdPrice is the ARB to USD conversion rate with 8 decimal places.
* The function first converts the ETH amount to USD, considering the
* decimals in ETH and the conversion rate.
* Then, it converts the USD amount to ARB, factoring in the decimals of ARB
* and its USD rate.
*
* @notice This function should return the expected amount in ARB when
* given an ETH
* amount in wei.
* @dev test the function with various inputs to ensure its accuracy.
*/
function calculateArbAmount(uint256 _ethAmountInWei)
internal
view
returns (uint256)
{
// Fetch the ETH to USD price with 8 decimal places
(, int256 ethUsdPrice,,,) = IChainLinkAggregator(
comptroller.getEthUsdAggregator()
).latestRoundData();
// Fetch the ARB to USD price with 8 decimal places
(, int256 arbUsdPrice,,,) =
IChainLinkAggregator(arbUsdAggregator).latestRoundData();
// Convert the ETH amount to USD
// _ethAmountInWei is in wei, ethUsdPrice has 8 decimal places.
// We multiply by 10^10 to adjust the final result to have 18 decimal
// places for ARB.
uint256 usdAmount =
(_ethAmountInWei * uint256(ethUsdPrice)) / rewardTokenDecimals;
// Convert the USD amount to ARB
// usdAmount is in USD with 18 decimals, arbUsdPrice has 8 decimal
// places.
// The result is in ARB's smallest unit (like wei for ETH).
uint256 arbAmount =
(usdAmount * rewardTokenDecimals) / uint256(arbUsdPrice);
return arbAmount;
}
/// @inheritdoc IRewardsClaimer
function getPendingRewards(
address _user,
bytes32 _platform
)
external
view
returns (uint256)
{
return userPendingRewards[_user][_platform];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for
/// the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard signed math utilities missing in the Solidity language.
*/
library SignedMath {
/**
* @dev Returns the largest of two signed numbers.
*/
function max(int256 a, int256 b) internal pure returns (int256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two signed numbers.
*/
function min(int256 a, int256 b) internal pure returns (int256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two signed numbers without overflow.
* The result is rounded towards zero.
*/
function average(int256 a, int256 b) internal pure returns (int256) {
// Formula from the book "Hacker's Delight"
int256 x = (a & b) + ((a ^ b) >> 1);
return x + (int256(uint256(x) >> 255) & (a ^ b));
}
/**
* @dev Returns the absolute unsigned value of a signed value.
*/
function abs(int256 n) internal pure returns (uint256) {
unchecked {
// must be unchecked in order to support `n = type(int256).min`
return uint256(n >= 0 ? n : -n);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Strings.sol)
pragma solidity ^0.8.20;
import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant HEX_DIGITS = "0123456789abcdef";
uint8 private constant ADDRESS_LENGTH = 20;
/**
* @dev The `value` string doesn't fit in the specified `length`.
*/
error StringsInsufficientHexLength(uint256 value, uint256 length);
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), HEX_DIGITS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `int256` to its ASCII `string` decimal representation.
*/
function toStringSigned(int256 value) internal pure returns (string memory) {
return string.concat(value < 0 ? "-" : "", toString(SignedMath.abs(value)));
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
uint256 localValue = value;
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = HEX_DIGITS[localValue & 0xf];
localValue >>= 4;
}
if (localValue != 0) {
revert StringsInsufficientHexLength(value, length);
}
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal
* representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), ADDRESS_LENGTH);
}
/**
* @dev Returns true if the two strings are equal.
*/
function equal(string memory a, string memory b) internal pure returns (bool) {
return bytes(a).length == bytes(b).length && keccak256(bytes(a)) == keccak256(bytes(b));
}
}
{
"compilationTarget": {
"src/rewards/RewardsClaimer.sol": "RewardsClaimer"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 2000
},
"remappings": [
":@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":@openzeppelin/=lib/openzeppelin-contracts/",
":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":@solady/=lib/solady/src/",
":@soladytokens/=lib/solady/src/tokens/",
":ds-test/=lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solady/=lib/solady/"
]
}
[{"inputs":[{"internalType":"address","name":"_comptroller","type":"address"},{"internalType":"address","name":"_rewardToken","type":"address"},{"internalType":"address","name":"_arbUsdAggregator","type":"address"},{"internalType":"uint128","name":"_maxClaimPerWallet","type":"uint128"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ComptrollerManager_EntityCannotBe0Address","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"NotComptrollerAdmin","type":"error"},{"inputs":[],"name":"NotProposedComptroller","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"RewardsClaimer_CannotClaimRewardsInTheSameBlock","type":"error"},{"inputs":[],"name":"RewardsClaimer_CannotSendTo0Address","type":"error"},{"inputs":[],"name":"RewardsClaimer_NotOwner","type":"error"},{"inputs":[],"name":"RewardsClaimer_NotPlatformLogic","type":"error"},{"inputs":[],"name":"RewardsClaimer_NotWhitelistedPlatform","type":"error"},{"inputs":[],"name":"RewardsClaimer_UserHasNoRewardsToClaimOrHasExceededClaimingAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"bytes32","name":"platform","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AddedRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldArbUsdAggregator","type":"address"},{"indexed":false,"internalType":"address","name":"newArbUsdAggregator","type":"address"}],"name":"ArbUsdAggregatorChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"bytes32","name":"platform","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimedRewards","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldComptroller","type":"address"},{"indexed":false,"internalType":"address","name":"newComptroller","type":"address"}],"name":"ComptrollerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"bytes32","name":"platform","type":"bytes32"}],"name":"NoRewardsAddedDueToLimit","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"totalAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountForIsolated","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountForCross","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountForIntent","type":"uint256"}],"name":"RewardTokensDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"inputs":[],"name":"acceptComptroller","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_feeAmountInEth","type":"uint256"},{"internalType":"bytes32","name":"_platform","type":"bytes32"}],"name":"addPendingUserRewards","outputs":[{"internalType":"uint256","name":"arbAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"arbUsdAggregator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"bytes32","name":"_platform","type":"bytes32"}],"name":"checkRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_platform","type":"bytes32"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"comptroller","outputs":[{"internalType":"contract IComptroller","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amountForIsolated","type":"uint256"},{"internalType":"uint256","name":"_amountForCross","type":"uint256"},{"internalType":"uint256","name":"_amountForIntent","type":"uint256"}],"name":"depositRewardTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_platform","type":"bytes32"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"editMaxArbRewardsToBeSpend","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_platform","type":"bytes32"},{"internalType":"bool","name":"_whitelisted","type":"bool"}],"name":"editWhitelistedPlatforms","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"bytes32","name":"_platform","type":"bytes32"}],"name":"getPendingRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gmxPlatform","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"lastRewardBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"platformHashed","type":"bytes32"}],"name":"maxArbRewardsToBeSpend","outputs":[{"internalType":"uint256","name":"maxArbRewardsSpend","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxClaimPerWallet","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proposedComptroller","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardTokenDecimals","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewardTokensLeft","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_arbUsdAggregator","type":"address"}],"name":"setArbUsdAggregator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_comptroller","type":"address"}],"name":"setComptroller","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"_maxClaimPerWallet","type":"uint128"}],"name":"setMaxClaimPerWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_rewardToken","type":"address"}],"name":"setRewardToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint128","name":"_rewardTokenDecimals","type":"uint128"}],"name":"setRewardTokenDecimals","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symmPlatform","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"platform","type":"bytes32"}],"name":"totalUnclaimedRewards","outputs":[{"internalType":"uint256","name":"rewardAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unPause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"userClaimedRewardsAcrossAllPlatforms","outputs":[{"internalType":"uint256","name":"rewardAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"vertexPlatform","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"platform","type":"bytes32"}],"name":"whitelistedPlatforms","outputs":[{"internalType":"bool","name":"whitelisted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_token","type":"address"}],"name":"withdrawNonRewardTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]