Created
September 3, 2025 07:03
-
-
Save cNoveron/8d6d9eb46dd8010b59b5127d016da9ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| pragma solidity ^0.5.16; | |
| pragma experimental ABIEncoderV2; | |
| import "./XToken.sol"; | |
| interface LexeLike { | |
| function delegate(address delegatee) external; | |
| } | |
| /** | |
| * @title Lendexe's XErc20 Contract | |
| * @notice XTokens which wrap an EIP-20 underlying | |
| * @author Lendexe | |
| */ | |
| contract XErc20 is XToken, XErc20Interface { | |
| /** | |
| * @notice Initialize the new money market | |
| * @param underlying_ The address of the underlying asset | |
| * @param comptroller_ The address of the Comptroller | |
| * @param interestRateModel_ The address of the interest rate model | |
| * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 | |
| * @param name_ ERC-20 name of this token | |
| * @param symbol_ ERC-20 symbol of this token | |
| * @param decimals_ ERC-20 decimal precision of this token | |
| */ | |
| function initialize( | |
| address underlying_, | |
| ComptrollerInterface comptroller_, | |
| InterestRateModel interestRateModel_, | |
| uint256 initialExchangeRateMantissa_, | |
| string memory name_, | |
| string memory symbol_, | |
| uint8 decimals_ | |
| ) public { | |
| // XToken initialize does the bulk of the work | |
| super.initialize( | |
| comptroller_, | |
| interestRateModel_, | |
| initialExchangeRateMantissa_, | |
| name_, | |
| symbol_, | |
| decimals_ | |
| ); | |
| // Set underlying and sanity check it | |
| underlying = underlying_; | |
| EIP20Interface(underlying).totalSupply(); | |
| } | |
| /*** User Interface ***/ | |
| /** | |
| * @notice Sender supplies assets into the market and receives xTokens in exchange | |
| * @dev Accrues interest whether or not the operation succeeds, unless reverted | |
| * @param mintAmount The amount of the underlying asset to supply | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function mint(uint256 mintAmount) external returns (uint256) { | |
| (uint256 err, ) = mintInternal(mintAmount); | |
| return err; | |
| } | |
| /** | |
| * @notice Sender redeems xTokens in exchange for the underlying asset | |
| * @dev Accrues interest whether or not the operation succeeds, unless reverted | |
| * @param redeemTokens The number of xTokens to redeem into underlying | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function redeem(uint256 redeemTokens) external returns (uint256) { | |
| return redeemInternal(redeemTokens); | |
| } | |
| /** | |
| * @notice Sender redeems xTokens in exchange for a specified amount of underlying asset | |
| * @dev Accrues interest whether or not the operation succeeds, unless reverted | |
| * @param redeemAmount The amount of underlying to redeem | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function redeemUnderlying(uint256 redeemAmount) external returns (uint256) { | |
| return redeemUnderlyingInternal(redeemAmount); | |
| } | |
| /** | |
| * @notice Sender borrows assets from the protocol to their own address | |
| * @param borrowAmount The amount of the underlying asset to borrow | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function borrow(uint256 borrowAmount) external returns (uint256) { | |
| return borrowInternal(borrowAmount); | |
| } | |
| /** | |
| * @notice Sender repays their own borrow | |
| * @param repayAmount The amount to repay | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function repayBorrow(uint256 repayAmount) external returns (uint256) { | |
| (uint256 err, ) = repayBorrowInternal(repayAmount); | |
| return err; | |
| } | |
| /** | |
| * @notice Sender repays a borrow belonging to borrower | |
| * @param borrower the account with the debt being payed off | |
| * @param repayAmount The amount to repay | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function repayBorrowBehalf( | |
| address borrower, | |
| uint256 repayAmount | |
| ) external returns (uint256) { | |
| (uint256 err, ) = repayBorrowBehalfInternal(borrower, repayAmount); | |
| return err; | |
| } | |
| /** | |
| * @notice The sender liquidates the borrowers collateral. | |
| * The collateral seized is transferred to the liquidator. | |
| * @param borrower The borrower of this xToken to be liquidated | |
| * @param repayAmount The amount of the underlying borrowed asset to repay | |
| * @param xTokenCollateral The market in which to seize collateral from the borrower | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function liquidateBorrow( | |
| address borrower, | |
| uint256 repayAmount, | |
| XTokenInterface xTokenCollateral | |
| ) external returns (uint256) { | |
| require( | |
| msg.sender == comptroller.liquidatorAddress(), | |
| "Only liquidator set by admin can liquidate!" | |
| ); | |
| (uint256 err, ) = liquidateBorrowInternal( | |
| borrower, | |
| repayAmount, | |
| xTokenCollateral | |
| ); | |
| return err; | |
| } | |
| /** | |
| * @notice A public function to sweep accidental ERC-20 transfers to this contract. Tokens are sent to admin (timelock) | |
| * @param token The address of the ERC-20 token to sweep | |
| */ | |
| function sweepToken(EIP20NonStandardInterface token) external { | |
| require( | |
| address(token) != underlying, | |
| "XErc20::sweepToken: can not sweep underlying token" | |
| ); | |
| uint256 balance = token.balanceOf(address(this)); | |
| token.transfer(admin, balance); | |
| } | |
| /** | |
| * @notice The sender adds to reserves. | |
| * @param addAmount The amount fo underlying token to add as reserves | |
| * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details) | |
| */ | |
| function _addReserves(uint256 addAmount) external returns (uint256) { | |
| return _addReservesInternal(addAmount); | |
| } | |
| /*** Safe Token ***/ | |
| function approveUnderlying(address spender, uint256 amount) internal { | |
| EIP20Interface token = EIP20Interface(underlying); | |
| token.approve(spender, amount); | |
| } | |
| /** | |
| * @notice Gets balance of this contract in terms of the underlying | |
| * @dev This excludes the value of the current message, if any | |
| * @return The quantity of underlying tokens owned by this contract | |
| */ | |
| function getCashPrior() internal view returns (uint256) { | |
| EIP20Interface token = EIP20Interface(underlying); | |
| return | |
| scaleToDecimal( | |
| token.decimals(), | |
| decimals, | |
| token.balanceOf(address(this)) | |
| ); | |
| } | |
| /** | |
| * @notice Gets decimal number of the underlying token | |
| * @return The decimal number of the underlying token | |
| */ | |
| function getUnderlyingDecimalsNumber() internal view returns (uint8) { | |
| return EIP20Interface(underlying).decimals(); | |
| } | |
| function getCash18Decimals() external view returns (uint256) { | |
| return getCashPrior(); | |
| } | |
| /** | |
| * @dev Similar to EIP20 transfer, except it handles a False result from `transferFrom` and reverts in that case. | |
| * This will revert due to insufficient balance or insufficient allowance. | |
| * This function returns the actual amount received, | |
| * which may be less than `amount` if there is a fee attached to the transfer. | |
| * | |
| * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. | |
| * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca | |
| */ | |
| function doTransferIn( | |
| address from, | |
| uint256 amount | |
| ) internal returns (uint256) { | |
| EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); | |
| uint8 _decimals = decimals; | |
| uint8 underlyingDecimals = token.decimals(); | |
| uint256 balanceBefore_underlying = token.balanceOf(address(this)); | |
| token.transferFrom( | |
| from, | |
| address(this), | |
| scaleToDecimal(_decimals, underlyingDecimals, amount) | |
| ); | |
| bool success; | |
| assembly { | |
| switch returndatasize() | |
| case 0 { | |
| // This is a non-standard ERC-20 | |
| success := not(0) // set success to true | |
| } | |
| case 32 { | |
| // This is a compliant ERC-20 | |
| returndatacopy(0, 0, 32) | |
| success := mload(0) // Set `success = returndata` of external call | |
| } | |
| default { | |
| // This is an excessively non-compliant ERC-20, revert. | |
| revert(0, 0) | |
| } | |
| } | |
| require(success, "TOKEN_TRANSFER_IN_FAILED"); | |
| // Calculate the amount that was *actually* transferred | |
| uint256 balanceAfter_underlying = token.balanceOf(address(this)); | |
| require(balanceAfter_underlying >= balanceBefore_underlying, "TOKEN_TRANSFER_IN_OVERFLOW"); | |
| return scaleToDecimal( | |
| underlyingDecimals, | |
| _decimals, | |
| balanceAfter_underlying - balanceBefore_underlying // underflow already checked above, just subtract | |
| ); | |
| } | |
| function scaleToDecimal( | |
| uint8 inputDecimals, | |
| uint8 outputDecimals, | |
| uint256 amount | |
| ) internal pure returns (uint256) { | |
| if (inputDecimals > outputDecimals) { | |
| return | |
| div_( | |
| Exp(amount), | |
| (10 ** uint256(inputDecimals - outputDecimals)) | |
| ).mantissa; | |
| } else { | |
| return | |
| mul_( | |
| Exp(amount), | |
| (10 ** uint256(outputDecimals - inputDecimals)) | |
| ).mantissa; | |
| } | |
| } | |
| /** | |
| * @dev Similar to EIP20 transfer, except it handles a False success from `transfer` and returns an explanatory | |
| * error code rather than reverting. If caller has not called checked protocol's balance, this may revert due to | |
| * insufficient cash held in this contract. If caller has checked protocol's balance prior to this call, and verified | |
| * it is >= amount, this should not revert in normal conditions. | |
| * | |
| * Note: This wrapper safely handles non-standard ERC-20 tokens that do not return a value. | |
| * See here: https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca | |
| */ | |
| function doTransferOut(address payable to, uint256 amount) internal { | |
| EIP20NonStandardInterface token = EIP20NonStandardInterface(underlying); | |
| token.transfer(to, scaleToDecimal(decimals, token.decimals(), amount)); | |
| bool success; | |
| assembly { | |
| switch returndatasize() | |
| case 0 { | |
| // This is a non-standard ERC-20 | |
| success := not(0) // set success to true | |
| } | |
| case 32 { | |
| // This is a compliant ERC-20 | |
| returndatacopy(0, 0, 32) | |
| success := mload(0) // Set `success = returndata` of external call | |
| } | |
| default { | |
| // This is an excessively non-compliant ERC-20, revert. | |
| revert(0, 0) | |
| } | |
| } | |
| require(success, "TOKEN_TRANSFER_OUT_FAILED"); | |
| } | |
| /** | |
| * @notice Admin call to delegate the votes of the LEXE-like underlying | |
| * @param lexeLikeDelegatee The address to delegate votes to | |
| * @dev XTokens whose underlying are not LexeLike should revert here | |
| */ | |
| function _delegateLexeLikeTo(address lexeLikeDelegatee) external { | |
| require( | |
| msg.sender == admin, | |
| "only the admin may set the lexe-like delegate" | |
| ); | |
| LexeLike(underlying).delegate(lexeLikeDelegatee); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment