Skip to content

Instantly share code, notes, and snippets.

@cNoveron
Created September 3, 2025 07:03
Show Gist options
  • Select an option

  • Save cNoveron/8d6d9eb46dd8010b59b5127d016da9ac to your computer and use it in GitHub Desktop.

Select an option

Save cNoveron/8d6d9eb46dd8010b59b5127d016da9ac to your computer and use it in GitHub Desktop.
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