Sonic Blaze Testnet
    /

    Contract Diff Checker

    Contract Name:
    UserProxy

    Contract Source Code:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";
    import {IERC20Permit} from "openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
    
    import {IUserProxy} from "./interfaces/IUserProxy.sol";
    import {ILQTYStaking} from "./interfaces/ILQTYStaking.sol";
    import {PermitParams} from "./utils/Types.sol";
    
    contract UserProxy is IUserProxy {
        /// @inheritdoc IUserProxy
        IERC20 public immutable lqty;
        /// @inheritdoc IUserProxy
        IERC20 public immutable lusd;
    
        /// @inheritdoc IUserProxy
        ILQTYStaking public immutable stakingV1;
        /// @inheritdoc IUserProxy
        address public immutable stakingV2;
    
        constructor(address _lqty, address _lusd, address _stakingV1) {
            lqty = IERC20(_lqty);
            lusd = IERC20(_lusd);
            stakingV1 = ILQTYStaking(_stakingV1);
            stakingV2 = msg.sender;
        }
    
        modifier onlyStakingV2() {
            require(msg.sender == stakingV2, "UserProxy: caller-not-stakingV2");
            _;
        }
    
        /// @inheritdoc IUserProxy
        function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient)
            public
            onlyStakingV2
            returns (uint256 lusdAmount, uint256 ethAmount)
        {
            uint256 initialLUSDAmount = lusd.balanceOf(address(this));
            uint256 initialETHAmount = address(this).balance;
    
            lqty.transferFrom(_lqtyFrom, address(this), _amount);
            stakingV1.stake(_amount);
            emit Stake(_amount, _lqtyFrom);
    
            if (_doSendRewards) {
                (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount);
            }
        }
    
        /// @inheritdoc IUserProxy
        function stakeViaPermit(
            uint256 _amount,
            address _lqtyFrom,
            PermitParams calldata _permitParams,
            bool _doSendRewards,
            address _recipient
        ) public onlyStakingV2 returns (uint256 lusdAmount, uint256 ethAmount) {
            require(_lqtyFrom == _permitParams.owner, "UserProxy: owner-not-sender");
    
            uint256 initialLUSDAmount = lusd.balanceOf(address(this));
            uint256 initialETHAmount = address(this).balance;
    
            try IERC20Permit(address(lqty)).permit(
                _permitParams.owner,
                _permitParams.spender,
                _permitParams.value,
                _permitParams.deadline,
                _permitParams.v,
                _permitParams.r,
                _permitParams.s
            ) {} catch {}
            stake(_amount, _lqtyFrom, _doSendRewards, _recipient);
    
            if (_doSendRewards) {
                (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount);
            }
        }
    
        /// @inheritdoc IUserProxy
        function unstake(uint256 _amount, bool _doSendRewards, address _recipient)
            public
            onlyStakingV2
            returns (uint256 lusdAmount, uint256 ethAmount)
        {
            uint256 initialLQTYAmount = lqty.balanceOf(address(this));
            uint256 initialLUSDAmount = lusd.balanceOf(address(this));
            uint256 initialETHAmount = address(this).balance;
    
            stakingV1.unstake(_amount);
    
            uint256 lqtyAmount = lqty.balanceOf(address(this));
            if (lqtyAmount > 0) lqty.transfer(_recipient, lqtyAmount);
    
            emit Unstake(_recipient, lqtyAmount - initialLQTYAmount, lqtyAmount);
    
            if (_doSendRewards) {
                (lusdAmount, ethAmount) = _sendRewards(_recipient, initialLUSDAmount, initialETHAmount);
            }
        }
    
        function _sendRewards(address _recipient, uint256 _initialLUSDAmount, uint256 _initialETHAmount)
            internal
            returns (uint256 lusdAmount, uint256 ethAmount)
        {
            lusdAmount = lusd.balanceOf(address(this));
            if (lusdAmount > 0) lusd.transfer(_recipient, lusdAmount);
            ethAmount = address(this).balance;
            if (ethAmount > 0) {
                (bool success,) = payable(_recipient).call{value: ethAmount}("");
                require(success, "UserProxy: eth-fail");
            }
    
            emit SendRewards(
                _recipient, lusdAmount - _initialLUSDAmount, lusdAmount, ethAmount - _initialETHAmount, ethAmount
            );
        }
    
        /// @inheritdoc IUserProxy
        function staked() external view returns (uint88) {
            return uint88(stakingV1.stakes(address(this)));
        }
    
        receive() external payable {}
    }

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)
    
    pragma solidity ^0.8.20;
    
    import {IERC20} from "../token/ERC20/IERC20.sol";

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
     *
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
     * need to send a transaction, and thus is not required to hold Ether at all.
     *
     * ==== Security Considerations
     *
     * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
     * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
     * considered as an intention to spend the allowance in any specific way. The second is that because permits have
     * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
     * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
     * generally recommended is:
     *
     * ```solidity
     * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
     *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
     *     doThing(..., value);
     * }
     *
     * function doThing(..., uint256 value) public {
     *     token.safeTransferFrom(msg.sender, address(this), value);
     *     ...
     * }
     * ```
     *
     * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
     * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
     * {SafeERC20-safeTransferFrom}).
     *
     * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
     * contracts should have entry points that don't rely on permit.
     */
    interface IERC20Permit {
        /**
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
         * given ``owner``'s signed approval.
         *
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
         * ordering also apply here.
         *
         * Emits an {Approval} event.
         *
         * Requirements:
         *
         * - `spender` cannot be the zero address.
         * - `deadline` must be a timestamp in the future.
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
         * over the EIP712-formatted function arguments.
         * - the signature must use ``owner``'s current nonce (see {nonces}).
         *
         * For more information on the signature format, see the
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
         * section].
         *
         * CAUTION: See Security Considerations above.
         */
        function permit(
            address owner,
            address spender,
            uint256 value,
            uint256 deadline,
            uint8 v,
            bytes32 r,
            bytes32 s
        ) external;
    
        /**
         * @dev Returns the current nonce for `owner`. This value must be
         * included whenever a signature is generated for {permit}.
         *
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
         * prevents a signature from being used multiple times.
         */
        function nonces(address owner) external view returns (uint256);
    
        /**
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
         */
        // solhint-disable-next-line func-name-mixedcase
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    import {IERC20} from "openzeppelin/contracts/interfaces/IERC20.sol";
    
    import {ILQTYStaking} from "../interfaces/ILQTYStaking.sol";
    
    import {PermitParams} from "../utils/Types.sol";
    
    interface IUserProxy {
        event Stake(uint256 amount, address lqtyFrom);
        event Unstake(address indexed lqtyRecipient, uint256 lqtyReceived, uint256 lqtySent);
        event SendRewards(
            address indexed recipient,
            uint256 lusdAmountReceived,
            uint256 lusdAmountSent,
            uint256 ethAmountReceived,
            uint256 ethAmountSent
        );
    
        /// @notice Address of the LQTY token
        /// @return lqty Address of the LQTY token
        function lqty() external view returns (IERC20 lqty);
        /// @notice Address of the LUSD token
        /// @return lusd Address of the LUSD token
        function lusd() external view returns (IERC20 lusd);
        /// @notice Address of the V1 LQTY staking contract
        /// @return stakingV1 Address of the V1 LQTY staking contract
        function stakingV1() external view returns (ILQTYStaking stakingV1);
        /// @notice Address of the V2 LQTY staking contract
        /// @return stakingV2 Address of the V2 LQTY staking contract
        function stakingV2() external view returns (address stakingV2);
    
        /// @notice Stakes a given amount of LQTY tokens in the V1 staking contract
        /// @dev The LQTY tokens must be approved for transfer by the user
        /// @param _amount Amount of LQTY tokens to stake
        /// @param _lqtyFrom Address from which to transfer the LQTY tokens
        /// @param _doSendRewards If true, send rewards claimed from LQTY staking
        /// @param _recipient Address to which the tokens should be sent
        /// @return lusdAmount Amount of LUSD tokens claimed
        /// @return ethAmount Amount of ETH claimed
        function stake(uint256 _amount, address _lqtyFrom, bool _doSendRewards, address _recipient)
            external
            returns (uint256 lusdAmount, uint256 ethAmount);
        /// @notice Stakes a given amount of LQTY tokens in the V1 staking contract using a permit
        /// @param _amount Amount of LQTY tokens to stake
        /// @param _lqtyFrom Address from which to transfer the LQTY tokens
        /// @param _permitParams Parameters for the permit data
        /// @param _doSendRewards If true, send rewards claimed from LQTY staking
        /// @param _recipient Address to which the tokens should be sent
        /// @return lusdAmount Amount of LUSD tokens claimed
        /// @return ethAmount Amount of ETH claimed
        function stakeViaPermit(
            uint256 _amount,
            address _lqtyFrom,
            PermitParams calldata _permitParams,
            bool _doSendRewards,
            address _recipient
        ) external returns (uint256 lusdAmount, uint256 ethAmount);
        /// @notice Unstakes a given amount of LQTY tokens from the V1 staking contract and claims the accrued rewards
        /// @param _amount Amount of LQTY tokens to unstake
        /// @param _doSendRewards If true, send rewards claimed from LQTY staking
        /// @param _recipient Address to which the tokens should be sent
        /// @return lusdAmount Amount of LUSD tokens claimed
        /// @return ethAmount Amount of ETH claimed
        function unstake(uint256 _amount, bool _doSendRewards, address _recipient)
            external
            returns (uint256 lusdAmount, uint256 ethAmount);
        /// @notice Returns the current amount LQTY staked by a user in the V1 staking contract
        /// @return staked Amount of LQTY tokens staked
        function staked() external view returns (uint88);
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    interface ILQTYStaking {
        // --- Events --
    
        event LQTYTokenAddressSet(address _lqtyTokenAddress);
        event LUSDTokenAddressSet(address _lusdTokenAddress);
        event TroveManagerAddressSet(address _troveManager);
        event BorrowerOperationsAddressSet(address _borrowerOperationsAddress);
        event ActivePoolAddressSet(address _activePoolAddress);
    
        event StakeChanged(address indexed staker, uint256 newStake);
        event StakingGainsWithdrawn(address indexed staker, uint256 LUSDGain, uint256 ETHGain);
        event F_ETHUpdated(uint256 _F_ETH);
        event F_LUSDUpdated(uint256 _F_LUSD);
        event TotalLQTYStakedUpdated(uint256 _totalLQTYStaked);
        event EtherSent(address _account, uint256 _amount);
        event StakerSnapshotsUpdated(address _staker, uint256 _F_ETH, uint256 _F_LUSD);
    
        // --- Functions ---
    
        function setAddresses(
            address _lqtyTokenAddress,
            address _lusdTokenAddress,
            address _troveManagerAddress,
            address _borrowerOperationsAddress,
            address _activePoolAddress
        ) external;
    
        function stake(uint256 _LQTYamount) external;
    
        function unstake(uint256 _LQTYamount) external;
    
        function increaseF_ETH(uint256 _ETHFee) external;
    
        function increaseF_LUSD(uint256 _LQTYFee) external;
    
        function getPendingETHGain(address _user) external view returns (uint256);
    
        function getPendingLUSDGain(address _user) external view returns (uint256);
    
        function stakes(address _user) external view returns (uint256);
    
        function totalLQTYStaked() external view returns (uint256);
    }

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.24;
    
    struct PermitParams {
        address owner;
        address spender;
        uint256 value;
        uint256 deadline;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }
    
    uint256 constant WAD = 1e18;

    // SPDX-License-Identifier: MIT
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
    
    pragma solidity ^0.8.20;
    
    /**
     * @dev Interface of the ERC20 standard as defined in the EIP.
     */
    interface IERC20 {
        /**
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
         * another (`to`).
         *
         * Note that `value` may be zero.
         */
        event Transfer(address indexed from, address indexed to, uint256 value);
    
        /**
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
         * a call to {approve}. `value` is the new allowance.
         */
        event Approval(address indexed owner, address indexed spender, uint256 value);
    
        /**
         * @dev Returns the value of tokens in existence.
         */
        function totalSupply() external view returns (uint256);
    
        /**
         * @dev Returns the value of tokens owned by `account`.
         */
        function balanceOf(address account) external view returns (uint256);
    
        /**
         * @dev Moves a `value` amount of tokens from the caller's account to `to`.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transfer(address to, uint256 value) external returns (bool);
    
        /**
         * @dev Returns the remaining number of tokens that `spender` will be
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
         * zero by default.
         *
         * This value changes when {approve} or {transferFrom} are called.
         */
        function allowance(address owner, address spender) external view returns (uint256);
    
        /**
         * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
         * caller's tokens.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
         * that someone may use both the old and the new allowance by unfortunate
         * transaction ordering. One possible solution to mitigate this race
         * condition is to first reduce the spender's allowance to 0 and set the
         * desired value afterwards:
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
         *
         * Emits an {Approval} event.
         */
        function approve(address spender, uint256 value) external returns (bool);
    
        /**
         * @dev Moves a `value` amount of tokens from `from` to `to` using the
         * allowance mechanism. `value` is then deducted from the caller's
         * allowance.
         *
         * Returns a boolean value indicating whether the operation succeeded.
         *
         * Emits a {Transfer} event.
         */
        function transferFrom(address from, address to, uint256 value) external returns (bool);
    }

    Please enter a contract address above to load the contract details and source code.

    Context size (optional):