Contract Name:
BenqiNativeERC4626Reinvest
Contract Source Code:
File 1 of 1 : BenqiNativeERC4626Reinvest
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.14;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
/*//////////////////////////////////////////////////////////////
ETH OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferETH(address to, uint256 amount) internal {
bool success;
assembly {
// Transfer the ETH and store if it succeeded or not.
success := call(gas(), to, amount, 0, 0, 0, 0)
}
require(success, "ETH_TRANSFER_FAILED");
}
/*//////////////////////////////////////////////////////////////
ERC20 OPERATIONS
//////////////////////////////////////////////////////////////*/
function safeTransferFrom(
ERC20 token,
address from,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
)
}
require(success, "TRANSFER_FROM_FAILED");
}
function safeTransfer(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "TRANSFER_FAILED");
}
function safeApprove(
ERC20 token,
address to,
uint256 amount
) internal {
bool success;
assembly {
// Get a pointer to some free memory.
let freeMemoryPointer := mload(0x40)
// Write the abi-encoded calldata into memory, beginning with the function selector.
mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.
success := and(
// Set success to whether the call reverted, if not we check it either
// returned exactly 1 (can't just be non-zero data), or had no return data.
or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
// We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
// We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
// Counterintuitively, this call must be positioned second to the or() call in the
// surrounding and() call or else returndatasize() will be zero during the computation.
call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
)
}
require(success, "APPROVE_FAILED");
}
}
/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
/*//////////////////////////////////////////////////////////////
SIMPLIFIED FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.
function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
}
function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
}
function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
}
function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
}
/*//////////////////////////////////////////////////////////////
LOW LEVEL FIXED POINT OPERATIONS
//////////////////////////////////////////////////////////////*/
function mulDivDown(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// Divide z by the denominator.
z := div(z, denominator)
}
}
function mulDivUp(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 z) {
assembly {
// Store x * y in z for now.
z := mul(x, y)
// Equivalent to require(denominator != 0 && (x == 0 || (x * y) / x == y))
if iszero(and(iszero(iszero(denominator)), or(iszero(x), eq(div(z, x), y)))) {
revert(0, 0)
}
// First, divide z - 1 by the denominator and add 1.
// We allow z - 1 to underflow if z is 0, because we multiply the
// end result by 0 if z is zero, ensuring we return 0 if z is zero.
z := mul(iszero(iszero(z)), add(div(sub(z, 1), denominator), 1))
}
}
function rpow(
uint256 x,
uint256 n,
uint256 scalar
) internal pure returns (uint256 z) {
assembly {
switch x
case 0 {
switch n
case 0 {
// 0 ** 0 = 1
z := scalar
}
default {
// 0 ** n = 0
z := 0
}
}
default {
switch mod(n, 2)
case 0 {
// If n is even, store scalar in z for now.
z := scalar
}
default {
// If n is odd, store x in z for now.
z := x
}
// Shifting right by 1 is like dividing by 2.
let half := shr(1, scalar)
for {
// Shift n right by 1 before looping to halve it.
n := shr(1, n)
} n {
// Shift n right by 1 each iteration to halve it.
n := shr(1, n)
} {
// Revert immediately if x ** 2 would overflow.
// Equivalent to iszero(eq(div(xx, x), x)) here.
if shr(128, x) {
revert(0, 0)
}
// Store x squared.
let xx := mul(x, x)
// Round to the nearest number.
let xxRound := add(xx, half)
// Revert if xx + half overflowed.
if lt(xxRound, xx) {
revert(0, 0)
}
// Set x to scaled xxRound.
x := div(xxRound, scalar)
// If n is even:
if mod(n, 2) {
// Compute z * x.
let zx := mul(z, x)
// If z * x overflowed:
if iszero(eq(div(zx, x), z)) {
// Revert if x is non-zero.
if iszero(iszero(x)) {
revert(0, 0)
}
}
// Round to the nearest number.
let zxRound := add(zx, half)
// Revert if zx + half overflowed.
if lt(zxRound, zx) {
revert(0, 0)
}
// Return properly scaled zxRound.
z := div(zxRound, scalar)
}
}
}
}
}
/*//////////////////////////////////////////////////////////////
GENERAL NUMBER UTILITIES
//////////////////////////////////////////////////////////////*/
function sqrt(uint256 x) internal pure returns (uint256 z) {
assembly {
// Start off with z at 1.
z := 1
// Used below to help find a nearby power of 2.
let y := x
// Find the lowest power of 2 that is at least sqrt(x).
if iszero(lt(y, 0x100000000000000000000000000000000)) {
y := shr(128, y) // Like dividing by 2 ** 128.
z := shl(64, z) // Like multiplying by 2 ** 64.
}
if iszero(lt(y, 0x10000000000000000)) {
y := shr(64, y) // Like dividing by 2 ** 64.
z := shl(32, z) // Like multiplying by 2 ** 32.
}
if iszero(lt(y, 0x100000000)) {
y := shr(32, y) // Like dividing by 2 ** 32.
z := shl(16, z) // Like multiplying by 2 ** 16.
}
if iszero(lt(y, 0x10000)) {
y := shr(16, y) // Like dividing by 2 ** 16.
z := shl(8, z) // Like multiplying by 2 ** 8.
}
if iszero(lt(y, 0x100)) {
y := shr(8, y) // Like dividing by 2 ** 8.
z := shl(4, z) // Like multiplying by 2 ** 4.
}
if iszero(lt(y, 0x10)) {
y := shr(4, y) // Like dividing by 2 ** 4.
z := shl(2, z) // Like multiplying by 2 ** 2.
}
if iszero(lt(y, 0x8)) {
// Equivalent to 2 ** z.
z := shl(1, z)
}
// Shifting right by 1 is like dividing by 2.
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
z := shr(1, add(z, div(x, z)))
// Compute a rounded down version of z.
let zRoundDown := div(x, z)
// If zRoundDown is smaller, use it.
if lt(zRoundDown, z) {
z := zRoundDown
}
}
}
}
/// @notice Minimal ERC4626 tokenized Vault implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/mixins/ERC4626.sol)
abstract contract ERC4626 is ERC20 {
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Deposit(address indexed caller, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed caller,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/
ERC20 public immutable asset;
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC20(_name, _symbol, _asset.decimals()) {
asset = _asset;
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LOGIC
//////////////////////////////////////////////////////////////*/
function deposit(uint256 assets, address receiver) public virtual returns (uint256 shares) {
// Check for rounding error since we round down in previewDeposit.
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) {
assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up.
// Need to transfer before minting or ERC777s could reenter.
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
require((assets = previewRedeem(shares)) != 0, "ZERO_ASSETS");
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
asset.safeTransfer(receiver, assets);
}
/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/
function totalAssets() public view virtual returns (uint256);
function convertToShares(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
}
function convertToAssets(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivDown(totalAssets(), supply);
}
function previewDeposit(uint256 assets) public view virtual returns (uint256) {
return convertToShares(assets);
}
function previewMint(uint256 shares) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? shares : shares.mulDivUp(totalAssets(), supply);
}
function previewWithdraw(uint256 assets) public view virtual returns (uint256) {
uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
return supply == 0 ? assets : assets.mulDivUp(supply, totalAssets());
}
function previewRedeem(uint256 shares) public view virtual returns (uint256) {
return convertToAssets(shares);
}
/*//////////////////////////////////////////////////////////////
DEPOSIT/WITHDRAWAL LIMIT LOGIC
//////////////////////////////////////////////////////////////*/
function maxDeposit(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxMint(address) public view virtual returns (uint256) {
return type(uint256).max;
}
function maxWithdraw(address owner) public view virtual returns (uint256) {
return convertToAssets(balanceOf[owner]);
}
function maxRedeem(address owner) public view virtual returns (uint256) {
return balanceOf[owner];
}
/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/
function beforeWithdraw(uint256 assets, uint256 shares) internal virtual {}
function afterDeposit(uint256 assets, uint256 shares) internal virtual {}
}
abstract contract ICEther is ERC20 {
function comptroller() external view virtual returns (address);
function getCash() external view virtual returns (uint256);
function getAccountSnapshot(address)
external
view
virtual
returns (
uint256,
uint256,
uint256,
uint256
);
function redeemUnderlying(uint256) external virtual returns (uint256);
function mint() external payable virtual;
function exchangeRateStored() external virtual view returns (uint);
}
interface IInterestRateModel {
function getBorrowRate(
uint256,
uint256,
uint256
) external view returns (uint256);
function getSupplyRate(
uint256,
uint256,
uint256,
uint256
) external view returns (uint256);
}
abstract contract ICERC20 is ERC20 {
function mint(uint256 underlyingAmount) external virtual returns (uint256);
function underlying() external view virtual returns (ERC20);
function getCash() external view virtual returns (uint256);
function totalBorrows() external view virtual returns (uint256);
function totalReserves() external view virtual returns (uint256);
function exchangeRateStored() external view virtual returns (uint256);
function accrualBlockNumber() external view virtual returns (uint256);
function redeemUnderlying(uint256 underlyingAmount)
external
virtual
returns (uint256);
function balanceOfUnderlying(address) external virtual returns (uint256);
function reserveFactorMantissa() external view virtual returns (uint256);
function interestRateModel()
external
view
virtual
returns (IInterestRateModel);
function initialExchangeRateMantissa()
external
view
virtual
returns (uint256);
}
/// @notice Get up to date cToken data without mutating state.
/// @author Transmissions11 (https://github.com/transmissions11/libcompound)
library LibCompound {
using FixedPointMathLib for uint256;
function viewUnderlyingBalanceOf(ICERC20 cToken, address user) internal view returns (uint256) {
return cToken.balanceOf(user).mulWadDown(viewExchangeRate(cToken));
}
function viewExchangeRate(ICERC20 cToken) internal view returns (uint256) {
uint256 accrualBlockNumberPrior = cToken.accrualBlockNumber();
if (accrualBlockNumberPrior == block.number) {
return cToken.exchangeRateStored();
}
uint256 totalCash = cToken.underlying().balanceOf(address(cToken));
uint256 borrowsPrior = cToken.totalBorrows();
uint256 reservesPrior = cToken.totalReserves();
uint256 borrowRateMantissa = cToken.interestRateModel().getBorrowRate(totalCash, borrowsPrior, reservesPrior);
require(borrowRateMantissa <= 0.0005e16, "RATE_TOO_HIGH"); // Same as borrowRateMaxMantissa in CTokenInterfaces.sol
uint256 interestAccumulated =
borrowRateMantissa * block.number - accrualBlockNumberPrior.mulWadDown(borrowsPrior);
uint256 totalReserves = cToken.reserveFactorMantissa().mulWadDown(interestAccumulated) + reservesPrior;
uint256 totalBorrows = interestAccumulated + borrowsPrior;
uint256 totalSupply = cToken.totalSupply();
return
totalSupply == 0
? cToken.initialExchangeRateMantissa()
: totalCash + totalBorrows - totalReserves.divWadDown(totalSupply);
}
}
interface IComptroller {
function qiAddress() external view returns (address);
function getAllMarkets() external view returns (address[] memory);
function allMarkets(uint256 index) external view returns (address);
function claimReward(uint8 rewardType, address holder) external;
function mintGuardianPaused(address cToken) external view returns (bool);
function rewardAccrued(uint8, address) external view returns (uint256);
}
interface IPair {
function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external;
}
library DexSwap {
using SafeTransferLib for ERC20;
/**
* @notice Swap directly through a Pair
* @param amountIn input amount
* @param fromToken address
* @param toToken address
* @param pairToken Pair used for swap
* @return output amount
*/
function swap(
uint256 amountIn,
address fromToken,
address toToken,
address pairToken
) internal returns (uint256) {
IPair pair = IPair(pairToken);
(address token0, ) = sortTokens(fromToken, toToken);
(uint112 reserve0, uint112 reserve1, ) = pair.getReserves();
if (token0 != fromToken) (reserve0, reserve1) = (reserve1, reserve0);
uint256 amountOut1 = 0;
uint256 amountOut2 = getAmountOut(amountIn, reserve0, reserve1);
if (token0 != fromToken)
(amountOut1, amountOut2) = (amountOut2, amountOut1);
ERC20(fromToken).safeTransfer(address(pair), amountIn);
pair.swap(amountOut1, amountOut2, address(this), new bytes(0));
return amountOut2 > amountOut1 ? amountOut2 : amountOut1;
}
/**
* @notice Given an input amount of an asset and pair reserves, returns maximum output amount of the other asset
* @dev Assumes swap fee is 0.30%
* @param amountIn input asset
* @param reserveIn size of input asset reserve
* @param reserveOut size of output asset reserve
* @return maximum output amount
*/
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256) {
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * (reserveOut);
uint256 denominator = (reserveIn * 1000) + (amountInWithFee);
return numerator / (denominator);
}
/**
* @notice Given two tokens, it'll return the tokens in the right order for the tokens pair
* @dev TokenA must be different from TokenB, and both shouldn't be address(0), no validations
* @param tokenA address
* @param tokenB address
* @return sorted tokens
*/
function sortTokens(address tokenA, address tokenB)
internal
pure
returns (address, address)
{
return tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
}
}
interface WrappedNative {
function deposit() external payable;
function withdraw(uint wad) external;
}
/// @title BenqiERC4626Reinvest - Custom implementation of yield-daddy wrappers with flexible reinvesting logic
/// @notice Extended with payable function to accept native token transfer
contract BenqiNativeERC4626Reinvest is ERC4626 {
/// -----------------------------------------------------------------------
/// Libraries usage
/// -----------------------------------------------------------------------
using LibCompound for ICEther;
using SafeTransferLib for ERC20;
using FixedPointMathLib for uint256;
/// -----------------------------------------------------------------------
/// Constants
/// -----------------------------------------------------------------------
uint256 internal constant NO_ERROR = 0;
/// -----------------------------------------------------------------------
/// Immutable params
/// -----------------------------------------------------------------------
/// @notice cEther token reference
ICEther public immutable cEther;
/// @notice The Compound comptroller contract
IComptroller public immutable comptroller;
/// @notice Access Control for harvest() route
address public immutable manager;
/// @notice The COMP-like token contract
ERC20 public immutable reward;
/// @notice Pointer to swapInfo
swapInfo public SwapInfo;
/// Compact struct to make two swaps (PancakeSwap on BSC)
/// A => B (using pair1) then B => asset (of Wrapper) (using pair2)
struct swapInfo {
address token;
address pair1;
address pair2;
}
/// -----------------------------------------------------------------------
/// Errors
/// -----------------------------------------------------------------------
/// @notice Thrown when a call to Compound returned an error.
/// @param errorCode The error code returned by Compound
error CompoundERC4626__CompoundError(uint256 errorCode);
/// @notice Thrown when the deposited assets doesnot return any shares.
error CompoundERC4626_ZEROSHARES_Error();
/// @notice Thrown when the redeems shares doesnot return any assets.
error CompoundERC4626_ZEROASSETS_Error();
/// -----------------------------------------------------------------------
/// Constructor
/// -----------------------------------------------------------------------
constructor(
ERC20 asset_, // underlying
ERC20 reward_, // comp token or other
ICEther cEther_, // compound concept of a share
address manager_
) ERC4626(asset_, _vaultName(asset_), _vaultSymbol(asset_)) {
reward = reward_;
cEther = cEther_;
comptroller = IComptroller(cEther.comptroller());
manager = manager_;
}
/// -----------------------------------------------------------------------
/// Compound liquidity mining
/// -----------------------------------------------------------------------
function setRoute(
address token,
address pair1,
address pair2
) external {
require(msg.sender == manager, "onlyOwner");
SwapInfo = swapInfo(token, pair1, pair2);
ERC20(reward).approve(SwapInfo.pair1, type(uint256).max); /// max approve
ERC20(SwapInfo.token).approve(SwapInfo.pair2, type(uint256).max); /// max approve
}
/// @notice Claims liquidity mining rewards from Compound and performs low-lvl swap with instant reinvesting
/// Calling harvest() claims COMP-Fork token through direct Pair swap for best control and lowest cost
/// harvest() can be called by anybody. ideally this function should be adjusted per needs (e.g add fee for harvesting)
function harvest() external {
ICEther[] memory cTokens = new ICEther[](1);
cTokens[0] = cEther;
/// TODO: Setter for rewardType
comptroller.claimReward(1, address(this));
uint256 earned = ERC20(reward).balanceOf(address(this));
address rewardToken = address(reward);
/// If only one swap needed (high liquidity pair) - set swapInfo.token0/token/pair2 to 0x
if (SwapInfo.token == address(asset)) {
DexSwap.swap(
earned, /// REWARDS amount to swap
rewardToken, // from REWARD (because of liquidity)
address(asset), /// to target underlying of this Vault ie USDC
SwapInfo.pair1 /// pairToken (pool)
);
/// If two swaps needed
} else {
uint256 swapTokenAmount = DexSwap.swap(
earned, /// REWARDS amount to swap
rewardToken, /// fromToken REWARD
SwapInfo.token, /// to intermediary token with high liquidity (no direct pools)
SwapInfo.pair1 /// pairToken (pool)
);
DexSwap.swap(
swapTokenAmount,
SwapInfo.token, // from received BUSD (because of liquidity)
address(asset), /// to target underlying of this Vault ie USDC
SwapInfo.pair2 /// pairToken (pool)
);
}
afterDeposit(asset.balanceOf(address(this)), 0);
}
/// -----------------------------------------------------------------------
/// ERC4626 overrides
/// -----------------------------------------------------------------------
function beforeWithdraw(uint256 assets, uint256) internal override {
// Withdraw the underlying tokens from the cEther.
uint256 errorCode = cEther.redeemUnderlying(assets);
if (errorCode != NO_ERROR) {
revert CompoundERC4626__CompoundError(errorCode);
}
}
function viewUnderlyingBalanceOf() internal view returns (uint256) {
return
cEther.balanceOf(address(this)).mulWadDown(
cEther.exchangeRateStored()
);
}
function afterDeposit(uint256 assets, uint256) internal override {
WrappedNative(address(asset)).withdraw(assets);
// mint tokens
cEther.mint{value: assets}();
}
function deposit(address receiver)
public
payable
returns (uint256 shares)
{
// Check for rounding error since we round down in previewDeposit.
if ((shares = previewDeposit(msg.value)) == 0)
revert CompoundERC4626_ZEROSHARES_Error();
require((shares = previewDeposit(msg.value)) != 0, "ZERO_SHARES");
WrappedNative(address(asset)).deposit{value: msg.value}();
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, msg.value, shares);
afterDeposit(msg.value, shares);
}
/// Standard ERC4626 deposit can only accept ERC20
function deposit(uint256 assets, address receiver)
public
override
returns (uint256 shares)
{
require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
asset.safeTransferFrom(msg.sender, address(this), assets);
_mint(receiver, shares);
emit Deposit(msg.sender, receiver, assets, shares);
afterDeposit(assets, shares);
}
/// @notice Total amount of the underlying asset that
/// is "managed" by Vault.
function totalAssets() public view override returns (uint256) {
return viewUnderlyingBalanceOf();
}
/// @notice maximum amount of assets that can be deposited.
/// This is capped by the amount of assets the cEther can be
/// supplied with.
/// This is 0 if minting is paused on the cEther.
function maxDeposit(address) public view override returns (uint256) {
if (comptroller.mintGuardianPaused(address(cEther))) return 0;
return type(uint256).max;
}
/// @notice maximum amount of shares that can be minted.
/// This is capped by the amount of assets the cEther can be
/// supplied with.
/// This is 0 if minting is paused on the cEther.
function maxMint(address) public view override returns (uint256) {
if (comptroller.mintGuardianPaused(address(cEther))) return 0;
return type(uint256).max;
}
/// @notice Maximum amount of assets that can be withdrawn.
/// This is capped by the amount of cash available on the cEther,
/// if all assets are borrowed, a user can't withdraw from the vault.
function maxWithdraw(address owner) public view override returns (uint256) {
uint256 cash = cEther.getCash();
uint256 assetsBalance = convertToAssets(balanceOf[owner]);
return cash < assetsBalance ? cash : assetsBalance;
}
/// @notice Maximum amount of shares that can be redeemed.
/// This is capped by the amount of cash available on the cEther,
/// if all assets are borrowed, a user can't redeem from the vault.
function maxRedeem(address owner) public view override returns (uint256) {
uint256 cash = cEther.getCash();
uint256 cashInShares = convertToShares(cash);
uint256 shareBalance = balanceOf[owner];
return cashInShares < shareBalance ? cashInShares : shareBalance;
}
/// @notice withdraw assets of the owner.
function withdraw(
uint256 assets,
address receiver,
address owner
) public override returns (uint256 shares) {
shares = previewWithdraw(assets); // No need to check for rounding error, previewWithdraw rounds up.
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max)
allowance[owner][msg.sender] = allowed - shares;
}
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
WrappedNative(address(asset)).deposit{value: assets}();
asset.safeTransfer(receiver, assets);
}
function redeem(
uint256 shares,
address receiver,
address owner
) public override returns (uint256 assets) {
if (msg.sender != owner) {
uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max)
allowance[owner][msg.sender] = allowed - shares;
}
// Check for rounding error since we round down in previewRedeem.
if ((assets = previewRedeem(shares)) == 0)
revert CompoundERC4626_ZEROASSETS_Error();
beforeWithdraw(assets, shares);
_burn(owner, shares);
emit Withdraw(msg.sender, receiver, owner, assets, shares);
WrappedNative(address(asset)).deposit{value: assets}();
asset.safeTransfer(receiver, assets);
}
receive() external payable {}
/// -----------------------------------------------------------------------
/// ERC20 metadata generation
/// -----------------------------------------------------------------------
function _vaultName(ERC20 asset_)
internal
view
virtual
returns (string memory vaultName)
{
vaultName = string.concat("ERC4626-Wrapped Benqi - ", asset_.symbol());
}
function _vaultSymbol(ERC20 asset_)
internal
view
virtual
returns (string memory vaultSymbol)
{
vaultSymbol = string.concat("bq46-", asset_.symbol());
}
}