// SPDX-License-Identifier: UNLICENSED
// Kashi Lending Medium Risk
// __ __ __ __ _____ __ __
// | |/ .---.-.-----| |--|__| | |_.-----.-----.--| |__.-----.-----.
// | <| _ |__ --| | | | | -__| | _ | | | _ |
// |__|\__|___._|_____|__|__|__| |_______|_____|__|__|_____|__|__|__|___ |
// |_____|
// Copyright (c) 2021 BoringCrypto - All rights reserved
// Twitter: @Boring_Crypto
// Special thanks to:
// @0xKeno - for all his invaluable contributions
// @burger_crypto - for the idea of trying to let the LPs benefit from liquidations
// Version: 22-Feb-2021
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
// solhint-disable avoid-low-level-calls
// solhint-disable no-inline-assembly
// solhint-disable not-rely-on-time
// File @boringcrypto/boring-solidity/contracts/libraries/BoringMath.sol@v1.2.0
// License-Identifier: MIT
/// @notice A library for performing overflow-/underflow-safe math,
/// updated with awesomeness from of DappHub (https://github.com/dapphub/ds-math).
library BoringMath {
function add(uint256 a, uint256 b) internal pure returns (uint256 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}
function sub(uint256 a, uint256 b) internal pure returns (uint256 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}
function mul(uint256 a, uint256 b) internal pure returns (uint256 c) {
require(b == 0 || (c = a * b) / b == a, "BoringMath: Mul Overflow");
}
function to128(uint256 a) internal pure returns (uint128 c) {
require(a <= uint128(-1), "BoringMath: uint128 Overflow");
c = uint128(a);
}
function to64(uint256 a) internal pure returns (uint64 c) {
require(a <= uint64(-1), "BoringMath: uint64 Overflow");
c = uint64(a);
}
function to32(uint256 a) internal pure returns (uint32 c) {
require(a <= uint32(-1), "BoringMath: uint32 Overflow");
c = uint32(a);
}
}
/// @notice A library for performing overflow-/underflow-safe addition and subtraction on uint128.
library BoringMath128 {
function add(uint128 a, uint128 b) internal pure returns (uint128 c) {
require((c = a + b) >= b, "BoringMath: Add Overflow");
}
function sub(uint128 a, uint128 b) internal pure returns (uint128 c) {
require((c = a - b) <= a, "BoringMath: Underflow");
}
}
// File @boringcrypto/boring-solidity/contracts/BoringOwnable.sol@v1.2.0
// License-Identifier: MIT
// Audit on 5-Jan-2021 by Keno and BoringCrypto
// Source: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol + Claimable.sol
// Edited by BoringCrypto
contract BoringOwnableData {
address public owner;
address public pendingOwner;
}
contract BoringOwnable is BoringOwnableData {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @notice `owner` defaults to msg.sender on construction.
constructor() public {
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
/// @notice Transfers ownership to `newOwner`. Either directly or claimable by the new pending owner.
/// Can only be invoked by the current `owner`.
/// @param newOwner Address of the new owner.
/// @param direct True if `newOwner` should be set immediately. False if `newOwner` needs to use `claimOwnership`.
/// @param renounce Allows the `newOwner` to be `address(0)` if `direct` and `renounce` is True. Has no effect otherwise.
function transferOwnership(
address newOwner,
bool direct,
bool renounce
) public onlyOwner {
if (direct) {
// Checks
require(newOwner != address(0) || renounce, "Ownable: zero address");
// Effects
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
pendingOwner = address(0);
} else {
// Effects
pendingOwner = newOwner;
}
}
/// @notice Needs to be called by `pendingOwner` to claim ownership.
function claimOwnership() public {
address _pendingOwner = pendingOwner;
// Checks
require(msg.sender == _pendingOwner, "Ownable: caller != pending owner");
// Effects
emit OwnershipTransferred(owner, _pendingOwner);
owner = _pendingOwner;
pendingOwner = address(0);
}
/// @notice Only allows the `owner` to execute the function.
modifier onlyOwner() {
require(msg.sender == owner, "Ownable: caller is not the owner");
_;
}
}
// File @boringcrypto/boring-solidity/contracts/Domain.sol@v1.2.0
// License-Identifier: MIT
// Based on code and smartness by Ross Campbell and Keno
// Uses immutable to store the domain separator to reduce gas usage
// If the chain id changes due to a fork, the forked chain will calculate on the fly.
contract Domain {
bytes32 private constant DOMAIN_SEPARATOR_SIGNATURE_HASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
// See https://eips.ethereum.org/EIPS/eip-191
string private constant EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA = "\x19\x01";
// solhint-disable var-name-mixedcase
bytes32 private immutable _DOMAIN_SEPARATOR;
uint256 private immutable DOMAIN_SEPARATOR_CHAIN_ID;
/// @dev Calculate the DOMAIN_SEPARATOR
function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
return keccak256(abi.encode(DOMAIN_SEPARATOR_SIGNATURE_HASH, chainId, address(this)));
}
constructor() public {
uint256 chainId;
assembly {
chainId := chainid()
}
_DOMAIN_SEPARATOR = _calculateDomainSeparator(DOMAIN_SEPARATOR_CHAIN_ID = chainId);
}
/// @dev Return the DOMAIN_SEPARATOR
// It's named internal to allow making it public from the contract that uses it by creating a simple view function
// with the desired public name, such as DOMAIN_SEPARATOR or domainSeparator.
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() public view returns (bytes32) {
uint256 chainId;
assembly {
chainId := chainid()
}
return chainId == DOMAIN_SEPARATOR_CHAIN_ID ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(chainId);
}
function _getDigest(bytes32 dataHash) internal view returns (bytes32 digest) {
digest = keccak256(abi.encodePacked(EIP191_PREFIX_FOR_EIP712_STRUCTURED_DATA, DOMAIN_SEPARATOR(), dataHash));
}
}
// File @boringcrypto/boring-solidity/contracts/ERC20.sol@v1.2.0
// License-Identifier: MIT
// solhint-disable no-inline-assembly
// solhint-disable not-rely-on-time
// Data part taken out for building of contracts that receive delegate calls
contract ERC20Data {
/// @notice owner > balance mapping.
mapping(address => uint256) public balanceOf;
/// @notice owner > spender > allowance mapping.
mapping(address => mapping(address => uint256)) public allowance;
/// @notice owner > nonce mapping. Used in `permit`.
mapping(address => uint256) public nonces;
}
contract ERC20 is ERC20Data, Domain {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
/// @notice Transfers `amount` tokens from `msg.sender` to `to`.
/// @param to The address to move the tokens.
/// @param amount of the tokens to move.
/// @return (bool) Returns True if succeeded.
function transfer(address to, uint256 amount) public returns (bool) {
// If `amount` is 0, or `msg.sender` is `to` nothing happens
if (amount != 0) {
uint256 srcBalance = balanceOf[msg.sender];
require(srcBalance >= amount, "ERC20: balance too low");
if (msg.sender != to) {
require(to != address(0), "ERC20: no zero address"); // Moved down so low balance calls safe some gas
balanceOf[msg.sender] = srcBalance - amount; // Underflow is checked
balanceOf[to] += amount; // Can't overflow because totalSupply would be greater than 2^256-1
}
}
emit Transfer(msg.sender, to, amount);
return true;
}
/// @notice Transfers `amount` tokens from `from` to `to`. Caller needs approval for `from`.
/// @param from Address to draw tokens from.
/// @param to The address to move the tokens.
/// @param amount The token amount to move.
/// @return (bool) Returns True if succeeded.
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
// If `amount` is 0, or `from` is `to` nothing happens
if (amount != 0) {
uint256 srcBalance = balanceOf[from];
require(srcBalance >= amount, "ERC20: balance too low");
if (from != to) {
uint256 spenderAllowance = allowance[from][msg.sender];
// If allowance is infinite, don't decrease it to save on gas (breaks with EIP-20).
if (spenderAllowance != type(uint256).max) {
require(spenderAllowance >= amount, "ERC20: allowance too low");
allowance[from][msg.sender] = spenderAllowance - amount; // Underflow is checked
}
require(to != address(0), "ERC20: no zero address"); // Moved down so other failed calls safe some gas
balanceOf[from] = srcBalance - amount; // Underflow is checked
balanceOf[to] += amount; // Can't overflow because totalSupply would be greater than 2^256-1
}
}
emit Transfer(from, to, amount);
return true;
}
/// @notice Approves `amount` from sender to be spend by `spender`.
/// @param spender Address of the party that can draw from msg.sender's account.
/// @param amount The maximum collective amount that `spender` can draw.
/// @return (bool) Returns True if approved.
function approve(address spender, uint256 amount) public returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 private constant PERMIT_SIGNATURE_HASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/// @notice Approves `value` from `owner_` to be spend by `spender`.
/// @param owner_ Address of the owner.
/// @param spender The address of the spender that gets approved to draw from `owner_`.
/// @param value The maximum collective amount that `spender` can draw.
/// @param deadline This permit must be redeemed before this deadline (UTC timestamp in seconds).
function permit(
address owner_,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(owner_ != address(0), "ERC20: Owner cannot be 0");
require(block.timestamp < deadline, "ERC20: Expired");
require(
ecrecover(_getDigest(keccak256(abi.encode(PERMIT_SIGNATURE_HASH, owner_, spender, value, nonces[owner_]++, deadline))), v, r, s) ==
owner_,
"ERC20: Invalid Signature"
);
allowance[owner_][spender] = value;
emit Approval(owner_, spender, value);
}
}
// File @boringcrypto/boring-solidity/contracts/interfaces/IMasterContract.sol@v1.2.0
// License-Identifier: MIT
interface IMasterContract {
/// @notice Init function that gets called from `BoringFactory.deploy`.
/// Also kown as the constructor for cloned contracts.
/// Any ETH send to `BoringFactory.deploy` ends up here.
/// @param data Can be abi encoded arguments or anything else.
function init(bytes calldata data) external payable;
}
// File @boringcrypto/boring-solidity/contracts/libraries/BoringRebase.sol@v1.2.0
// License-Identifier: MIT
struct Rebase {
uint128 elastic;
uint128 base;
}
/// @notice A rebasing library using overflow-/underflow-safe math.
library RebaseLibrary {
using BoringMath for uint256;
using BoringMath128 for uint128;
/// @notice Calculates the base value in relationship to `elastic` and `total`.
function toBase(
Rebase memory total,
uint256 elastic,
bool roundUp
) internal pure returns (uint256 base) {
if (total.elastic == 0) {
base = elastic;
} else {
base = elastic.mul(total.base) / total.elastic;
if (roundUp && base.mul(total.elastic) / total.base < elastic) {
base = base.add(1);
}
}
}
/// @notice Calculates the elastic value in relationship to `base` and `total`.
function toElastic(
Rebase memory total,
uint256 base,
bool roundUp
) internal pure returns (uint256 elastic) {
if (total.base == 0) {
elastic = base;
} else {
elastic = base.mul(total.elastic) / total.base;
if (roundUp && elastic.mul(total.base) / total.elastic < base) {
elastic = elastic.add(1);
}
}
}
/// @notice Add `elastic` to `total` and doubles `total.base`.
/// @return (Rebase) The new total.
/// @return base in relationship to `elastic`.
function add(
Rebase memory total,
uint256 elastic,
bool roundUp
) internal pure returns (Rebase memory, uint256 base) {
base = toBase(total, elastic, roundUp);
total.elastic = total.elastic.add(elastic.to128());
total.base = total.base.add(base.to128());
return (total, base);
}
/// @notice Sub `base` from `total` and update `total.elastic`.
/// @return (Rebase) The new total.
/// @return elastic in relationship to `base`.
function sub(
Rebase memory total,
uint256 base,
bool roundUp
) internal pure returns (Rebase memory, uint256 elastic) {
elastic = toElastic(total, base, roundUp);
total.elastic = total.elastic.sub(elastic.to128());
total.base = total.base.sub(base.to128());
return (total, elastic);
}
/// @notice Add `elastic` and `base` to `total`.
function add(
Rebase memory total,
uint256 elastic,
uint256 base
) internal pure returns (Rebase memory) {
total.elastic = total.elastic.add(elastic.to128());
total.base = total.base.add(base.to128());
return total;
}
/// @notice Subtract `elastic` and `base` to `total`.
function sub(
Rebase memory total,
uint256 elastic,
uint256 base
) internal pure returns (Rebase memory) {
total.elastic = total.elastic.sub(elastic.to128());
total.base = total.base.sub(base.to128());
return total;
}
}
// File @boringcrypto/boring-solidity/contracts/interfaces/IERC20.sol@v1.2.0
// License-Identifier: MIT
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @notice EIP 2612
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
}
// File @boringcrypto/boring-solidity/contracts/libraries/BoringERC20.sol@v1.2.0
// License-Identifier: MIT
library BoringERC20 {
bytes4 private constant SIG_SYMBOL = 0x95d89b41; // symbol()
bytes4 private constant SIG_NAME = 0x06fdde03; // name()
bytes4 private constant SIG_DECIMALS = 0x313ce567; // decimals()
bytes4 private constant SIG_TRANSFER = 0xa9059cbb; // transfer(address,uint256)
bytes4 private constant SIG_TRANSFER_FROM = 0x23b872dd; // transferFrom(address,address,uint256)
function returnDataToString(bytes memory data) internal pure returns (string memory) {
if (data.length >= 64) {
return abi.decode(data, (string));
} else if (data.length == 32) {
uint8 i = 0;
while (i < 32 && data[i] != 0) {
i++;
}
bytes memory bytesArray = new bytes(i);
for (i = 0; i < 32 && data[i] != 0; i++) {
bytesArray[i] = data[i];
}
return string(bytesArray);
} else {
return "???";
}
}
/// @notice Provides a safe ERC20.symbol version which returns '???' as fallback string.
/// @param token The address of the ERC-20 token contract.
/// @return (string) Token symbol.
function safeSymbol(IERC20 token) internal view returns (string memory) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_SYMBOL));
return success ? returnDataToString(data) : "???";
}
/// @notice Provides a safe ERC20.name version which returns '???' as fallback string.
/// @param token The address of the ERC-20 token contract.
/// @return (string) Token name.
function safeName(IERC20 token) internal view returns (string memory) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_NAME));
return success ? returnDataToString(data) : "???";
}
/// @notice Provides a safe ERC20.decimals version which returns '18' as fallback value.
/// @param token The address of the ERC-20 token contract.
/// @return (uint8) Token decimals.
function safeDecimals(IERC20 token) internal view returns (uint8) {
(bool success, bytes memory data) = address(token).staticcall(abi.encodeWithSelector(SIG_DECIMALS));
return success && data.length == 32 ? abi.decode(data, (uint8)) : 18;
}
}
// File @sushiswap/bentobox-sdk/contracts/IBatchFlashBorrower.sol@v1.0.1
// License-Identifier: MIT
interface IBatchFlashBorrower {
function onBatchFlashLoan(
address sender,
IERC20[] calldata tokens,
uint256[] calldata amounts,
uint256[] calldata fees,
bytes calldata data
) external;
}
// File @sushiswap/bentobox-sdk/contracts/IFlashBorrower.sol@v1.0.1
// License-Identifier: MIT
interface IFlashBorrower {
function onFlashLoan(
address sender,
IERC20 token,
uint256 amount,
uint256 fee,
bytes calldata data
) external;
}
// File @sushiswap/bentobox-sdk/contracts/IStrategy.sol@v1.0.1
// License-Identifier: MIT
interface IStrategy {
// Send the assets to the Strategy and call skim to invest them
function skim(uint256 amount) external;
// Harvest any profits made converted to the asset and pass them to the caller
function harvest(uint256 balance, address sender) external returns (int256 amountAdded);
// Withdraw assets. The returned amount can differ from the requested amount due to rounding.
// The actualAmount should be very close to the amount. The difference should NOT be used to report a loss. That's what harvest is for.
function withdraw(uint256 amount) external returns (uint256 actualAmount);
// Withdraw all assets in the safest way possible. This shouldn't fail.
function exit(uint256 balance) external returns (int256 amountAdded);
}
// File @sushiswap/bentobox-sdk/contracts/IBentoBoxV1.sol@v1.0.1
// License-Identifier: MIT
interface IBentoBoxV1 {
event LogDeploy(address indexed masterContract, bytes data, address indexed cloneAddress);
event LogDeposit(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 share);
event LogFlashLoan(address indexed borrower, address indexed token, uint256 amount, uint256 feeAmount, address indexed receiver);
event LogRegisterProtocol(address indexed protocol);
event LogSetMasterContractApproval(address indexed masterContract, address indexed user, bool approved);
event LogStrategyDivest(address indexed token, uint256 amount);
event LogStrategyInvest(address indexed token, uint256 amount);
event LogStrategyLoss(address indexed token, uint256 amount);
event LogStrategyProfit(address indexed token, uint256 amount);
event LogStrategyQueued(address indexed token, address indexed strategy);
event LogStrategySet(address indexed token, address indexed strategy);
event LogStrategyTargetPercentage(address indexed token, uint256 targetPercentage);
event LogTransfer(address indexed token, address indexed from, address indexed to, uint256 share);
event LogWhiteListMasterContract(address indexed masterContract, bool approved);
event LogWithdraw(address indexed token, address indexed from, address indexed to, uint256 amount, uint256 share);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
function balanceOf(IERC20, address) external view returns (uint256);
function batch(bytes[] calldata calls, bool revertOnFail) external payable returns (bool[] memory successes, bytes[] memory results);
function batchFlashLoan(
IBatchFlashBorrower borrower,
address[] calldata receivers,
IERC20[] calldata tokens,
uint256[] calldata amounts,
bytes calldata data
) external;
function claimOwnership() external;
function deploy(
address masterContract,
bytes calldata data,
bool useCreate2
) external payable;
function deposit(
IERC20 token_,
address from,
address to,
uint256 amount,
uint256 share
) external payable returns (uint256 amountOut, uint256 shareOut);
function flashLoan(
IFlashBorrower borrower,
address receiver,
IERC20 token,
uint256 amount,
bytes calldata data
) external;
function harvest(
IERC20 token,
bool balance,
uint256 maxChangeAmount
) external;
function masterContractApproved(address, address) external view returns (bool);
function masterContractOf(address) external view returns (address);
function nonces(address) external view returns (uint256);
function owner() external view returns (address);
function pendingOwner() external view returns (address);
function pendingStrategy(IERC20) external view returns (IStrategy);
function permitToken(
IERC20 token,
address from,
address to,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
function registerProtocol() external;
function setMasterContractApproval(
address user,
address masterContract,
bool approved,
uint8 v,
bytes32 r,
bytes32 s
) external;
function setStrategy(IERC20 token, IStrategy newStrategy) external;
function setStrategyTargetPercentage(IERC20 token, uint64 targetPercentage_) external;
function strategy(IERC20) external view returns (IStrategy);
function strategyData(IERC20)
external
view
returns (
uint64 strategyStartDate,
uint64 targetPercentage,
uint128 balance
);
function toAmount(
IERC20 token,
uint256 share,
bool roundUp
) external view returns (uint256 amount);
function toShare(
IERC20 token,
uint256 amount,
bool roundUp
) external view returns (uint256 share);
function totals(IERC20) external view returns (Rebase memory totals_);
function transfer(
IERC20 token,
address from,
address to,
uint256 share
) external;
function transferMultiple(
IERC20 token,
address from,
address[] calldata tos,
uint256[] calldata shares
) external;
function transferOwnership(
address newOwner,
bool direct,
bool renounce
) external;
function whitelistMasterContract(address masterContract, bool approved) external;
function whitelistedMasterContracts(address) external view returns (bool);
function withdraw(
IERC20 token_,
address from,
address to,
uint256 amount,
uint256 share
) external returns (uint256 amountOut, uint256 shareOut);
}
// File contracts/interfaces/IOracle.sol
// License-Identifier: MIT
interface IOracle {
/// @notice Get the latest exchange rate.
/// @param data Usually abi encoded, implementation specific data that contains information and arguments to & about the oracle.
/// For example:
/// (string memory collateralSymbol, string memory assetSymbol, uint256 division) = abi.decode(data, (string, string, uint256));
/// @return success if no valid (recent) rate is available, return false else true.
/// @return rate The rate of the requested asset / pair / pool.
function get(bytes calldata data) external returns (bool success, uint256 rate);
/// @notice Check the last exchange rate without any state changes.
/// @param data Usually abi encoded, implementation specific data that contains information and arguments to & about the oracle.
/// For example:
/// (string memory collateralSymbol, string memory assetSymbol, uint256 division) = abi.decode(data, (string, string, uint256));
/// @return success if no valid (recent) rate is available, return false else true.
/// @return rate The rate of the requested asset / pair / pool.
function peek(bytes calldata data) external view returns (bool success, uint256 rate);
/// @notice Check the current spot exchange rate without any state changes. For oracles like TWAP this will be different from peek().
/// @param data Usually abi encoded, implementation specific data that contains information and arguments to & about the oracle.
/// For example:
/// (string memory collateralSymbol, string memory assetSymbol, uint256 division) = abi.decode(data, (string, string, uint256));
/// @return rate The rate of the requested asset / pair / pool.
function peekSpot(bytes calldata data) external view returns (uint256 rate);
/// @notice Returns a human readable (short) name about this oracle.
/// @param data Usually abi encoded, implementation specific data that contains information and arguments to & about the oracle.
/// For example:
/// (string memory collateralSymbol, string memory assetSymbol, uint256 division) = abi.decode(data, (string, string, uint256));
/// @return (string) A human readable symbol name about this oracle.
function symbol(bytes calldata data) external view returns (string memory);
/// @notice Returns a human readable name about this oracle.
/// @param data Usually abi encoded, implementation specific data that contains information and arguments to & about the oracle.
/// For example:
/// (string memory collateralSymbol, string memory assetSymbol, uint256 division) = abi.decode(data, (string, string, uint256));
/// @return (string) A human readable name about this oracle.
function name(bytes calldata data) external view returns (string memory);
}
// File contracts/interfaces/ISwapper.sol
// License-Identifier: MIT
interface ISwapper {
/// @notice Withdraws 'amountFrom' of token 'from' from the BentoBox account for this swapper.
/// Swaps it for at least 'amountToMin' of token 'to'.
/// Transfers the swapped tokens of 'to' into the BentoBox using a plain ERC20 transfer.
/// Returns the amount of tokens 'to' transferred to BentoBox.
/// (The BentoBox skim function will be used by the caller to get the swapped funds).
function swap(
IERC20 fromToken,
IERC20 toToken,
address recipient,
uint256 shareToMin,
uint256 shareFrom
) external returns (uint256 extraShare, uint256 shareReturned);
/// @notice Calculates the amount of token 'from' needed to complete the swap (amountFrom),
/// this should be less than or equal to amountFromMax.
/// Withdraws 'amountFrom' of token 'from' from the BentoBox account for this swapper.
/// Swaps it for exactly 'exactAmountTo' of token 'to'.
/// Transfers the swapped tokens of 'to' into the BentoBox using a plain ERC20 transfer.
/// Transfers allocated, but unused 'from' tokens within the BentoBox to 'refundTo' (amountFromMax - amountFrom).
/// Returns the amount of 'from' tokens withdrawn from BentoBox (amountFrom).
/// (The BentoBox skim function will be used by the caller to get the swapped funds).
function swapExact(
IERC20 fromToken,
IERC20 toToken,
address recipient,
address refundTo,
uint256 shareFromSupplied,
uint256 shareToExact
) external returns (uint256 shareUsed, uint256 shareReturned);
}
// File contracts/KashiPair.sol
// License-Identifier: UNLICENSED
// Kashi Lending Medium Risk
/// @title KashiPair
/// @dev This contract allows contract calls to any contract (except BentoBox)
/// from arbitrary callers thus, don't trust calls from this contract in any circumstances.
contract KashiPairMediumRiskV1 is ERC20, BoringOwnable, IMasterContract {
using BoringMath for uint256;
using BoringMath128 for uint128;
using RebaseLibrary for Rebase;
using BoringERC20 for IERC20;
event LogExchangeRate(uint256 rate);
event LogAccrue(uint256 accruedAmount, uint256 feeFraction, uint64 rate, uint256 utilization);
event LogAddCollateral(address indexed from, address indexed to, uint256 share);
event LogAddAsset(address indexed from, address indexed to, uint256 share, uint256 fraction);
event LogRemoveCollateral(address indexed from, address indexed to, uint256 share);
event LogRemoveAsset(address indexed from, address indexed to, uint256 share, uint256 fraction);
event LogBorrow(address indexed from, address indexed to, uint256 amount, uint256 feeAmount, uint256 part);
event LogRepay(address indexed from, address indexed to, uint256 amount, uint256 part);
event LogFeeTo(address indexed newFeeTo);
event LogWithdrawFees(address indexed feeTo, uint256 feesEarnedFraction);
// Immutables (for MasterContract and all clones)
IBentoBoxV1 public immutable bentoBox;
KashiPairMediumRiskV1 public immutable masterContract;
// MasterContract variables
address public feeTo;
mapping(ISwapper => bool) public swappers;
// Per clone variables
// Clone init settings
IERC20 public collateral;
IERC20 public asset;
IOracle public oracle;
bytes public oracleData;
// Total amounts
uint256 public totalCollateralShare; // Total collateral supplied
Rebase public totalAsset; // elastic = BentoBox shares held by the KashiPair, base = Total fractions held by asset suppliers
Rebase public totalBorrow; // elastic = Total token amount to be repayed by borrowers, base = Total parts of the debt held by borrowers
// User balances
mapping(address => uint256) public userCollateralShare;
// userAssetFraction is called balanceOf for ERC20 compatibility (it's in ERC20.sol)
mapping(address => uint256) public userBorrowPart;
/// @notice Exchange and interest rate tracking.
/// This is 'cached' here because calls to Oracles can be very expensive.
uint256 public exchangeRate;
struct AccrueInfo {
uint64 interestPerSecond;
uint64 lastAccrued;
uint128 feesEarnedFraction;
}
AccrueInfo public accrueInfo;
// ERC20 'variables'
function symbol() external view returns (string memory) {
return string(abi.encodePacked("km", collateral.safeSymbol(), "/", asset.safeSymbol(), "-", oracle.symbol(oracleData)));
}
function name() external view returns (string memory) {
return string(abi.encodePacked("Kashi Medium Risk ", collateral.safeName(), "/", asset.safeName(), "-", oracle.name(oracleData)));
}
function decimals() external view returns (uint8) {
return asset.safeDecimals();
}
// totalSupply for ERC20 compatibility
function totalSupply() public view returns (uint256) {
return totalAsset.base;
}
// Settings for the Medium Risk KashiPair
uint256 private constant CLOSED_COLLATERIZATION_RATE = 75000; // 75%
uint256 private constant OPEN_COLLATERIZATION_RATE = 77000; // 77%
uint256 private constant COLLATERIZATION_RATE_PRECISION = 1e5; // Must be less than EXCHANGE_RATE_PRECISION (due to optimization in math)
uint256 private constant MINIMUM_TARGET_UTILIZATION = 7e17; // 70%
uint256 private constant MAXIMUM_TARGET_UTILIZATION = 8e17; // 80%
uint256 private constant UTILIZATION_PRECISION = 1e18;
uint256 private constant FULL_UTILIZATION = 1e18;
uint256 private constant FULL_UTILIZATION_MINUS_MAX = FULL_UTILIZATION - MAXIMUM_TARGET_UTILIZATION;
uint256 private constant FACTOR_PRECISION = 1e18;
uint64 private constant STARTING_INTEREST_PER_SECOND = 317097920; // approx 1% APR
uint64 private constant MINIMUM_INTEREST_PER_SECOND = 79274480; // approx 0.25% APR
uint64 private constant MAXIMUM_INTEREST_PER_SECOND = 317097920000; // approx 1000% APR
uint256 private constant INTEREST_ELASTICITY = 28800e36; // Half or double in 28800 seconds (8 hours) if linear
uint256 private constant EXCHANGE_RATE_PRECISION = 1e18;
uint256 private constant LIQUIDATION_MULTIPLIER = 112000; // add 12%
uint256 private constant LIQUIDATION_MULTIPLIER_PRECISION = 1e5;
// Fees
uint256 private constant PROTOCOL_FEE = 10000; // 10%
uint256 private constant PROTOCOL_FEE_DIVISOR = 1e5;
uint256 private constant BORROW_OPENING_FEE = 50; // 0.05%
uint256 private constant BORROW_OPENING_FEE_PRECISION = 1e5;
/// @notice The constructor is only used for the initial master contract. Subsequent clones are initialised via `init`.
constructor(IBentoBoxV1 bentoBox_) public {
bentoBox = bentoBox_;
masterContract = this;
}
/// @notice Serves as the constructor for clones, as clones can't have a regular constructor
/// @dev `data` is abi encoded in the format: (IERC20 collateral, IERC20 asset, IOracle oracle, bytes oracleData)
function init(bytes calldata data) public payable override {
require(address(collateral) == address(0), "KashiPair: already initialized");
(collateral, asset, oracle, oracleData) = abi.decode(data, (IERC20, IERC20, IOracle, bytes));
require(address(collateral) != address(0), "KashiPair: bad pair");
accrueInfo.interestPerSecond = uint64(STARTING_INTEREST_PER_SECOND); // 1% APR, with 1e18 being 100%
}
/// @notice Accrues the interest on the borrowed tokens and handles the accumulation of fees.
function accrue() public {
AccrueInfo memory _accrueInfo = accrueInfo;
// Number of seconds since accrue was called
uint256 elapsedTime = block.timestamp - _accrueInfo.lastAccrued;
if (elapsedTime == 0) {
return;
}
_accrueInfo.lastAccrued = uint64(block.timestamp);
Rebase memory _totalBorrow = totalBorrow;
if (_totalBorrow.base == 0) {
// If there are no borrows, reset the interest rate
if (_accrueInfo.interestPerSecond != STARTING_INTEREST_PER_SECOND) {
_accrueInfo.interestPerSecond = STARTING_INTEREST_PER_SECOND;
emit LogAccrue(0, 0, STARTING_INTEREST_PER_SECOND, 0);
}
accrueInfo = _accrueInfo;
return;
}
uint256 extraAmount = 0;
uint256 feeFraction = 0;
Rebase memory _totalAsset = totalAsset;
// Accrue interest
extraAmount = uint256(_totalBorrow.elastic).mul(_accrueInfo.interestPerSecond).mul(elapsedTime) / 1e18;
_totalBorrow.elastic = _totalBorrow.elastic.add(extraAmount.to128());
uint256 fullAssetAmount = bentoBox.toAmount(asset, _totalAsset.elastic, false).add(_totalBorrow.elastic);
uint256 feeAmount = extraAmount.mul(PROTOCOL_FEE) / PROTOCOL_FEE_DIVISOR; // % of interest paid goes to fee
feeFraction = feeAmount.mul(_totalAsset.base) / fullAssetAmount;
_accrueInfo.feesEarnedFraction = _accrueInfo.feesEarnedFraction.add(feeFraction.to128());
totalAsset.base = _totalAsset.base.add(feeFraction.to128());
totalBorrow = _totalBorrow;
// Update interest rate
uint256 utilization = uint256(_totalBorrow.elastic).mul(UTILIZATION_PRECISION) / fullAssetAmount;
if (utilization < MINIMUM_TARGET_UTILIZATION) {
uint256 underFactor = MINIMUM_TARGET_UTILIZATION.sub(utilization).mul(FACTOR_PRECISION) / MINIMUM_TARGET_UTILIZATION;
uint256 scale = INTEREST_ELASTICITY.add(underFactor.mul(underFactor).mul(elapsedTime));
_accrueInfo.interestPerSecond = uint64(uint256(_accrueInfo.interestPerSecond).mul(INTEREST_ELASTICITY) / scale);
if (_accrueInfo.interestPerSecond < MINIMUM_INTEREST_PER_SECOND) {
_accrueInfo.interestPerSecond = MINIMUM_INTEREST_PER_SECOND; // 0.25% APR minimum
}
} else if (utilization > MAXIMUM_TARGET_UTILIZATION) {
uint256 overFactor = utilization.sub(MAXIMUM_TARGET_UTILIZATION).mul(FACTOR_PRECISION) / FULL_UTILIZATION_MINUS_MAX;
uint256 scale = INTEREST_ELASTICITY.add(overFactor.mul(overFactor).mul(elapsedTime));
uint256 newInterestPerSecond = uint256(_accrueInfo.interestPerSecond).mul(scale) / INTEREST_ELASTICITY;
if (newInterestPerSecond > MAXIMUM_INTEREST_PER_SECOND) {
newInterestPerSecond = MAXIMUM_INTEREST_PER_SECOND; // 1000% APR maximum
}
_accrueInfo.interestPerSecond = uint64(newInterestPerSecond);
}
emit LogAccrue(extraAmount, feeFraction, _accrueInfo.interestPerSecond, utilization);
accrueInfo = _accrueInfo;
}
/// @notice Concrete implementation of `isSolvent`. Includes a third parameter to allow caching `exchangeRate`.
/// @param _exchangeRate The exchange rate. Used to cache the `exchangeRate` between calls.
function _isSolvent(
address user,
bool open,
uint256 _exchangeRate
) internal view returns (bool) {
// accrue must have already been called!
uint256 borrowPart = userBorrowPart[user];
if (borrowPart == 0) return true;
uint256 collateralShare = userCollateralShare[user];
if (collateralShare == 0) return false;
Rebase memory _totalBorrow = totalBorrow;
return
bentoBox.toAmount(
collateral,
collateralShare.mul(EXCHANGE_RATE_PRECISION / COLLATERIZATION_RATE_PRECISION).mul(
open ? OPEN_COLLATERIZATION_RATE : CLOSED_COLLATERIZATION_RATE
),
false
) >=
// Moved exchangeRate here instead of dividing the other side to preserve more precision
borrowPart.mul(_totalBorrow.elastic).mul(_exchangeRate) / _totalBorrow.base;
}
/// @dev Checks if the user is solvent in the closed liquidation case at the end of the function body.
modifier solvent() {
_;
require(_isSolvent(msg.sender, false, exchangeRate), "KashiPair: user insolvent");
}
/// @notice Gets the exchange rate. I.e how much collateral to buy 1e18 asset.
/// This function is supposed to be invoked if needed because Oracle queries can be expensive.
/// @return updated True if `exchangeRate` was updated.
/// @return rate The new exchange rate.
function updateExchangeRate() public returns (bool updated, uint256 rate) {
(updated, rate) = oracle.get(oracleData);
if (updated) {
exchangeRate = rate;
emit LogExchangeRate(rate);
} else {
// Return the old rate if fetching wasn't successful
rate = exchangeRate;
}
}
/// @dev Helper function to move tokens.
/// @param token The ERC-20 token.
/// @param share The amount in shares to add.
/// @param total Grand total amount to deduct from this contract's balance. Only applicable if `skim` is True.
/// Only used for accounting checks.
/// @param skim If True, only does a balance check on this contract.
/// False if tokens from msg.sender in `bentoBox` should be transferred.
function _addTokens(
IERC20 token,
uint256 share,
uint256 total,
bool skim
) internal {
if (skim) {
require(share <= bentoBox.balanceOf(token, address(this)).sub(total), "KashiPair: Skim too much");
} else {
bentoBox.transfer(token, msg.sender, address(this), share);
}
}
/// @notice Adds `collateral` from msg.sender to the account `to`.
/// @param to The receiver of the tokens.
/// @param skim True if the amount should be skimmed from the deposit balance of msg.sender.
/// False if tokens from msg.sender in `bentoBox` should be transferred.
/// @param share The amount of shares to add for `to`.
function addCollateral(
address to,
bool skim,
uint256 share
) public {
userCollateralShare[to] = userCollateralShare[to].add(share);
uint256 oldTotalCollateralShare = totalCollateralShare;
totalCollateralShare = oldTotalCollateralShare.add(share);
_addTokens(collateral, share, oldTotalCollateralShare, skim);
emit LogAddCollateral(skim ? address(bentoBox) : msg.sender, to, share);
}
/// @dev Concrete implementation of `removeCollateral`.
function _removeCollateral(address to, uint256 share) internal {
userCollateralShare[msg.sender] = userCollateralShare[msg.sender].sub(share);
totalCollateralShare = totalCollateralShare.sub(share);
emit LogRemoveCollateral(msg.sender, to, share);
bentoBox.transfer(collateral, address(this), to, share);
}
/// @notice Removes `share` amount of collateral and transfers it to `to`.
/// @param to The receiver of the shares.
/// @param share Amount of shares to remove.
function removeCollateral(address to, uint256 share) public solvent {
// accrue must be called because we check solvency
accrue();
_removeCollateral(to, share);
}
/// @dev Concrete implementation of `addAsset`.
function _addAsset(
address to,
bool skim,
uint256 share
) internal returns (uint256 fraction) {
Rebase memory _totalAsset = totalAsset;
uint256 totalAssetShare = _totalAsset.elastic;
uint256 allShare = _totalAsset.elastic + bentoBox.toShare(asset, totalBorrow.elastic, true);
fraction = allShare == 0 ? share : share.mul(_totalAsset.base) / allShare;
if (_totalAsset.base.add(fraction.to128()) < 1000) {
return 0;
}
totalAsset = _totalAsset.add(share, fraction);
balanceOf[to] = balanceOf[to].add(fraction);
emit Transfer(address(0), to, fraction);
_addTokens(asset, share, totalAssetShare, skim);
emit LogAddAsset(skim ? address(bentoBox) : msg.sender, to, share, fraction);
}
/// @notice Adds assets to the lending pair.
/// @param to The address of the user to receive the assets.
/// @param skim True if the amount should be skimmed from the deposit balance of msg.sender.
/// False if tokens from msg.sender in `bentoBox` should be transferred.
/// @param share The amount of shares to add.
/// @return fraction Total fractions added.
function addAsset(
address to,
bool skim,
uint256 share
) public returns (uint256 fraction) {
accrue();
fraction = _addAsset(to, skim, share);
}
/// @dev Concrete implementation of `removeAsset`.
function _removeAsset(address to, uint256 fraction) internal returns (uint256 share) {
Rebase memory _totalAsset = totalAsset;
uint256 allShare = _totalAsset.elastic + bentoBox.toShare(asset, totalBorrow.elastic, true);
share = fraction.mul(allShare) / _totalAsset.base;
balanceOf[msg.sender] = balanceOf[msg.sender].sub(fraction);
emit Transfer(msg.sender, address(0), fraction);
_totalAsset.elastic = _totalAsset.elastic.sub(share.to128());
_totalAsset.base = _totalAsset.base.sub(fraction.to128());
require(_totalAsset.base >= 1000, "Kashi: below minimum");
totalAsset = _totalAsset;
emit LogRemoveAsset(msg.sender, to, share, fraction);
bentoBox.transfer(asset, address(this), to, share);
}
/// @notice Removes an asset from msg.sender and transfers it to `to`.
/// @param to The user that receives the removed assets.
/// @param fraction The amount/fraction of assets held to remove.
/// @return share The amount of shares transferred to `to`.
function removeAsset(address to, uint256 fraction) public returns (uint256 share) {
accrue();
share = _removeAsset(to, fraction);
}
/// @dev Concrete implementation of `borrow`.
function _borrow(address to, uint256 amount) internal returns (uint256 part, uint256 share) {
uint256 feeAmount = amount.mul(BORROW_OPENING_FEE) / BORROW_OPENING_FEE_PRECISION; // A flat % fee is charged for any borrow
(totalBorrow, part) = totalBorrow.add(amount.add(feeAmount), true);
userBorrowPart[msg.sender] = userBorrowPart[msg.sender].add(part);
emit LogBorrow(msg.sender, to, amount, feeAmount, part);
share = bentoBox.toShare(asset, amount, false);
Rebase memory _totalAsset = totalAsset;
require(_totalAsset.base >= 1000, "Kashi: below minimum");
_totalAsset.elastic = _totalAsset.elastic.sub(share.to128());
totalAsset = _totalAsset;
bentoBox.transfer(asset, address(this), to, share);
}
/// @notice Sender borrows `amount` and transfers it to `to`.
/// @return part Total part of the debt held by borrowers.
/// @return share Total amount in shares borrowed.
function borrow(address to, uint256 amount) public solvent returns (uint256 part, uint256 share) {
accrue();
(part, share) = _borrow(to, amount);
}
/// @dev Concrete implementation of `repay`.
function _repay(
address to,
bool skim,
uint256 part
) internal returns (uint256 amount) {
(totalBorrow, amount) = totalBorrow.sub(part, true);
userBorrowPart[to] = userBorrowPart[to].sub(part);
uint256 share = bentoBox.toShare(asset, amount, true);
uint128 totalShare = totalAsset.elastic;
_addTokens(asset, share, uint256(totalShare), skim);
totalAsset.elastic = totalShare.add(share.to128());
emit LogRepay(skim ? address(bentoBox) : msg.sender, to, amount, part);
}
/// @notice Repays a loan.
/// @param to Address of the user this payment should go.
/// @param skim True if the amount should be skimmed from the deposit balance of msg.sender.
/// False if tokens from msg.sender in `bentoBox` should be transferred.
/// @param part The amount to repay. See `userBorrowPart`.
/// @return amount The total amount repayed.
function repay(
address to,
bool skim,
uint256 part
) public returns (uint256 amount) {
accrue();
amount = _repay(to, skim, part);
}
// Functions that need accrue to be called
uint8 internal constant ACTION_ADD_ASSET = 1;
uint8 internal constant ACTION_REPAY = 2;
uint8 internal constant ACTION_REMOVE_ASSET = 3;
uint8 internal constant ACTION_REMOVE_COLLATERAL = 4;
uint8 internal constant ACTION_BORROW = 5;
uint8 internal constant ACTION_GET_REPAY_SHARE = 6;
uint8 internal constant ACTION_GET_REPAY_PART = 7;
uint8 internal constant ACTION_ACCRUE = 8;
// Functions that don't need accrue to be called
uint8 internal constant ACTION_ADD_COLLATERAL = 10;
uint8 internal constant ACTION_UPDATE_EXCHANGE_RATE = 11;
// Function on BentoBox
uint8 internal constant ACTION_BENTO_DEPOSIT = 20;
uint8 internal constant ACTION_BENTO_WITHDRAW = 21;
uint8 internal constant ACTION_BENTO_TRANSFER = 22;
uint8 internal constant ACTION_BENTO_TRANSFER_MULTIPLE = 23;
uint8 internal constant ACTION_BENTO_SETAPPROVAL = 24;
// Any external call (except to BentoBox)
uint8 internal constant ACTION_CALL = 30;
int256 internal constant USE_VALUE1 = -1;
int256 internal constant USE_VALUE2 = -2;
/// @dev Helper function for choosing the correct value (`value1` or `value2`) depending on `inNum`.
function _num(
int256 inNum,
uint256 value1,
uint256 value2
) internal pure returns (uint256 outNum) {
outNum = inNum >= 0 ? uint256(inNum) : (inNum == USE_VALUE1 ? value1 : value2);
}
/// @dev Helper function for depositing into `bentoBox`.
function _bentoDeposit(
bytes memory data,
uint256 value,
uint256 value1,
uint256 value2
) internal returns (uint256, uint256) {
(IERC20 token, address to, int256 amount, int256 share) = abi.decode(data, (IERC20, address, int256, int256));
amount = int256(_num(amount, value1, value2)); // Done this way to avoid stack too deep errors
share = int256(_num(share, value1, value2));
return bentoBox.deposit{value: value}(token, msg.sender, to, uint256(amount), uint256(share));
}
/// @dev Helper function to withdraw from the `bentoBox`.
function _bentoWithdraw(
bytes memory data,
uint256 value1,
uint256 value2
) internal returns (uint256, uint256) {
(IERC20 token, address to, int256 amount, int256 share) = abi.decode(data, (IERC20, address, int256, int256));
return bentoBox.withdraw(token, msg.sender, to, _num(amount, value1, value2), _num(share, value1, value2));
}
/// @dev Helper function to perform a contract call and eventually extracting revert messages on failure.
/// Calls to `bentoBox` are not allowed for obvious security reasons.
/// This also means that calls made from this contract shall *not* be trusted.
function _call(
uint256 value,
bytes memory data,
uint256 value1,
uint256 value2
) internal returns (bytes memory, uint8) {
(address callee, bytes memory callData, bool useValue1, bool useValue2, uint8 returnValues) =
abi.decode(data, (address, bytes, bool, bool, uint8));
if (useValue1 && !useValue2) {
callData = abi.encodePacked(callData, value1);
} else if (!useValue1 && useValue2) {
callData = abi.encodePacked(callData, value2);
} else if (useValue1 && useValue2) {
callData = abi.encodePacked(callData, value1, value2);
}
require(callee != address(bentoBox) && callee != address(this), "KashiPair: can't call");
(bool success, bytes memory returnData) = callee.call{value: value}(callData);
require(success, "KashiPair: call failed");
return (returnData, returnValues);
}
struct CookStatus {
bool needsSolvencyCheck;
bool hasAccrued;
}
/// @notice Executes a set of actions and allows composability (contract calls) to other contracts.
/// @param actions An array with a sequence of actions to execute (see ACTION_ declarations).
/// @param values A one-to-one mapped array to `actions`. ETH amounts to send along with the actions.
/// Only applicable to `ACTION_CALL`, `ACTION_BENTO_DEPOSIT`.
/// @param datas A one-to-one mapped array to `actions`. Contains abi encoded data of function arguments.
/// @return value1 May contain the first positioned return value of the last executed action (if applicable).
/// @return value2 May contain the second positioned return value of the last executed action which returns 2 values (if applicable).
function cook(
uint8[] calldata actions,
uint256[] calldata values,
bytes[] calldata datas
) external payable returns (uint256 value1, uint256 value2) {
CookStatus memory status;
for (uint256 i = 0; i < actions.length; i++) {
uint8 action = actions[i];
if (!status.hasAccrued && action < 10) {
accrue();
status.hasAccrued = true;
}
if (action == ACTION_ADD_COLLATERAL) {
(int256 share, address to, bool skim) = abi.decode(datas[i], (int256, address, bool));
addCollateral(to, skim, _num(share, value1, value2));
} else if (action == ACTION_ADD_ASSET) {
(int256 share, address to, bool skim) = abi.decode(datas[i], (int256, address, bool));
value1 = _addAsset(to, skim, _num(share, value1, value2));
} else if (action == ACTION_REPAY) {
(int256 part, address to, bool skim) = abi.decode(datas[i], (int256, address, bool));
_repay(to, skim, _num(part, value1, value2));
} else if (action == ACTION_REMOVE_ASSET) {
(int256 fraction, address to) = abi.decode(datas[i], (int256, address));
value1 = _removeAsset(to, _num(fraction, value1, value2));
} else if (action == ACTION_REMOVE_COLLATERAL) {
(int256 share, address to) = abi.decode(datas[i], (int256, address));
_removeCollateral(to, _num(share, value1, value2));
status.needsSolvencyCheck = true;
} else if (action == ACTION_BORROW) {
(int256 amount, address to) = abi.decode(datas[i], (int256, address));
(value1, value2) = _borrow(to, _num(amount, value1, value2));
status.needsSolvencyCheck = true;
} else if (action == ACTION_UPDATE_EXCHANGE_RATE) {
(bool must_update, uint256 minRate, uint256 maxRate) = abi.decode(datas[i], (bool, uint256, uint256));
(bool updated, uint256 rate) = updateExchangeRate();
require((!must_update || updated) && rate > minRate && (maxRate == 0 || rate > maxRate), "KashiPair: rate not ok");
} else if (action == ACTION_BENTO_SETAPPROVAL) {
(address user, address _masterContract, bool approved, uint8 v, bytes32 r, bytes32 s) =
abi.decode(datas[i], (address, address, bool, uint8, bytes32, bytes32));
bentoBox.setMasterContractApproval(user, _masterContract, approved, v, r, s);
} else if (action == ACTION_BENTO_DEPOSIT) {
(value1, value2) = _bentoDeposit(datas[i], values[i], value1, value2);
} else if (action == ACTION_BENTO_WITHDRAW) {
(value1, value2) = _bentoWithdraw(datas[i], value1, value2);
} else if (action == ACTION_BENTO_TRANSFER) {
(IERC20 token, address to, int256 share) = abi.decode(datas[i], (IERC20, address, int256));
bentoBox.transfer(token, msg.sender, to, _num(share, value1, value2));
} else if (action == ACTION_BENTO_TRANSFER_MULTIPLE) {
(IERC20 token, address[] memory tos, uint256[] memory shares) = abi.decode(datas[i], (IERC20, address[], uint256[]));
bentoBox.transferMultiple(token, msg.sender, tos, shares);
} else if (action == ACTION_CALL) {
(bytes memory returnData, uint8 returnValues) = _call(values[i], datas[i], value1, value2);
if (returnValues == 1) {
(value1) = abi.decode(returnData, (uint256));
} else if (returnValues == 2) {
(value1, value2) = abi.decode(returnData, (uint256, uint256));
}
} else if (action == ACTION_GET_REPAY_SHARE) {
int256 part = abi.decode(datas[i], (int256));
value1 = bentoBox.toShare(asset, totalBorrow.toElastic(_num(part, value1, value2), true), true);
} else if (action == ACTION_GET_REPAY_PART) {
int256 amount = abi.decode(datas[i], (int256));
value1 = totalBorrow.toBase(_num(amount, value1, value2), false);
}
}
if (status.needsSolvencyCheck) {
require(_isSolvent(msg.sender, false, exchangeRate), "KashiPair: user insolvent");
}
}
/// @notice Handles the liquidation of users' balances, once the users' amount of collateral is too low.
/// @param users An array of user addresses.
/// @param maxBorrowParts A one-to-one mapping to `users`, contains maximum (partial) borrow amounts (to liquidate) of the respective user.
/// @param to Address of the receiver in open liquidations if `swapper` is zero.
/// @param swapper Contract address of the `ISwapper` implementation. Swappers are restricted for closed liquidations. See `setSwapper`.
/// @param open True to perform a open liquidation else False.
function liquidate(
address[] calldata users,
uint256[] calldata maxBorrowParts,
address to,
ISwapper swapper,
bool open
) public {
// Oracle can fail but we still need to allow liquidations
(, uint256 _exchangeRate) = updateExchangeRate();
accrue();
uint256 allCollateralShare;
uint256 allBorrowAmount;
uint256 allBorrowPart;
Rebase memory _totalBorrow = totalBorrow;
Rebase memory bentoBoxTotals = bentoBox.totals(collateral);
for (uint256 i = 0; i < users.length; i++) {
address user = users[i];
if (!_isSolvent(user, open, _exchangeRate)) {
uint256 borrowPart;
{
uint256 availableBorrowPart = userBorrowPart[user];
borrowPart = maxBorrowParts[i] > availableBorrowPart ? availableBorrowPart : maxBorrowParts[i];
userBorrowPart[user] = availableBorrowPart.sub(borrowPart);
}
uint256 borrowAmount = _totalBorrow.toElastic(borrowPart, false);
uint256 collateralShare =
bentoBoxTotals.toBase(
borrowAmount.mul(LIQUIDATION_MULTIPLIER).mul(_exchangeRate) /
(LIQUIDATION_MULTIPLIER_PRECISION * EXCHANGE_RATE_PRECISION),
false
);
userCollateralShare[user] = userCollateralShare[user].sub(collateralShare);
emit LogRemoveCollateral(user, swapper == ISwapper(0) ? to : address(swapper), collateralShare);
emit LogRepay(swapper == ISwapper(0) ? msg.sender : address(swapper), user, borrowAmount, borrowPart);
// Keep totals
allCollateralShare = allCollateralShare.add(collateralShare);
allBorrowAmount = allBorrowAmount.add(borrowAmount);
allBorrowPart = allBorrowPart.add(borrowPart);
}
}
require(allBorrowAmount != 0, "KashiPair: all are solvent");
_totalBorrow.elastic = _totalBorrow.elastic.sub(allBorrowAmount.to128());
_totalBorrow.base = _totalBorrow.base.sub(allBorrowPart.to128());
totalBorrow = _totalBorrow;
totalCollateralShare = totalCollateralShare.sub(allCollateralShare);
uint256 allBorrowShare = bentoBox.toShare(asset, allBorrowAmount, true);
if (!open) {
// Closed liquidation using a pre-approved swapper for the benefit of the LPs
require(masterContract.swappers(swapper), "KashiPair: Invalid swapper");
// Swaps the users' collateral for the borrowed asset
bentoBox.transfer(collateral, address(this), address(swapper), allCollateralShare);
swapper.swap(collateral, asset, address(this), allBorrowShare, allCollateralShare);
uint256 returnedShare = bentoBox.balanceOf(asset, address(this)).sub(uint256(totalAsset.elastic));
uint256 extraShare = returnedShare.sub(allBorrowShare);
uint256 feeShare = extraShare.mul(PROTOCOL_FEE) / PROTOCOL_FEE_DIVISOR; // % of profit goes to fee
// solhint-disable-next-line reentrancy
bentoBox.transfer(asset, address(this), masterContract.feeTo(), feeShare);
totalAsset.elastic = totalAsset.elastic.add(returnedShare.sub(feeShare).to128());
emit LogAddAsset(address(swapper), address(this), extraShare.sub(feeShare), 0);
} else {
// Swap using a swapper freely chosen by the caller
// Open (flash) liquidation: get proceeds first and provide the borrow after
bentoBox.transfer(collateral, address(this), swapper == ISwapper(0) ? to : address(swapper), allCollateralShare);
if (swapper != ISwapper(0)) {
swapper.swap(collateral, asset, msg.sender, allBorrowShare, allCollateralShare);
}
bentoBox.transfer(asset, msg.sender, address(this), allBorrowShare);
totalAsset.elastic = totalAsset.elastic.add(allBorrowShare.to128());
}
}
/// @notice Withdraws the fees accumulated.
function withdrawFees() public {
accrue();
address _feeTo = masterContract.feeTo();
uint256 _feesEarnedFraction = accrueInfo.feesEarnedFraction;
balanceOf[_feeTo] = balanceOf[_feeTo].add(_feesEarnedFraction);
emit Transfer(address(0), _feeTo, _feesEarnedFraction);
accrueInfo.feesEarnedFraction = 0;
emit LogWithdrawFees(_feeTo, _feesEarnedFraction);
}
/// @notice Used to register and enable or disable swapper contracts used in closed liquidations.
/// MasterContract Only Admin function.
/// @param swapper The address of the swapper contract that conforms to `ISwapper`.
/// @param enable True to enable the swapper. To disable use False.
function setSwapper(ISwapper swapper, bool enable) public onlyOwner {
swappers[swapper] = enable;
}
/// @notice Sets the beneficiary of fees accrued in liquidations.
/// MasterContract Only Admin function.
/// @param newFeeTo The address of the receiver.
function setFeeTo(address newFeeTo) public onlyOwner {
feeTo = newFeeTo;
emit LogFeeTo(newFeeTo);
}
}
{
"compilationTarget": {
"KashiPairMediumRiskV1.sol": "KashiPairMediumRiskV1"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 350
},
"remappings": []
}
[{"inputs":[{"internalType":"contract IBentoBoxV1","name":"bentoBox_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"accruedAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeFraction","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"rate","type":"uint64"},{"indexed":false,"internalType":"uint256","name":"utilization","type":"uint256"}],"name":"LogAccrue","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"share","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fraction","type":"uint256"}],"name":"LogAddAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"share","type":"uint256"}],"name":"LogAddCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"part","type":"uint256"}],"name":"LogBorrow","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"rate","type":"uint256"}],"name":"LogExchangeRate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newFeeTo","type":"address"}],"name":"LogFeeTo","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"share","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fraction","type":"uint256"}],"name":"LogRemoveAsset","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"share","type":"uint256"}],"name":"LogRemoveCollateral","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"part","type":"uint256"}],"name":"LogRepay","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"feeTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"feesEarnedFraction","type":"uint256"}],"name":"LogWithdrawFees","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accrue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"accrueInfo","outputs":[{"internalType":"uint64","name":"interestPerSecond","type":"uint64"},{"internalType":"uint64","name":"lastAccrued","type":"uint64"},{"internalType":"uint128","name":"feesEarnedFraction","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"skim","type":"bool"},{"internalType":"uint256","name":"share","type":"uint256"}],"name":"addAsset","outputs":[{"internalType":"uint256","name":"fraction","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"skim","type":"bool"},{"internalType":"uint256","name":"share","type":"uint256"}],"name":"addCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"asset","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bentoBox","outputs":[{"internalType":"contract IBentoBoxV1","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"borrow","outputs":[{"internalType":"uint256","name":"part","type":"uint256"},{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"collateral","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8[]","name":"actions","type":"uint8[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"bytes[]","name":"datas","type":"bytes[]"}],"name":"cook","outputs":[{"internalType":"uint256","name":"value1","type":"uint256"},{"internalType":"uint256","name":"value2","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exchangeRate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"init","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address[]","name":"users","type":"address[]"},{"internalType":"uint256[]","name":"maxBorrowParts","type":"uint256[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"contract ISwapper","name":"swapper","type":"address"},{"internalType":"bool","name":"open","type":"bool"}],"name":"liquidate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"masterContract","outputs":[{"internalType":"contract KashiPairMediumRiskV1","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracle","outputs":[{"internalType":"contract IOracle","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oracleData","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner_","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"fraction","type":"uint256"}],"name":"removeAsset","outputs":[{"internalType":"uint256","name":"share","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"share","type":"uint256"}],"name":"removeCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"bool","name":"skim","type":"bool"},{"internalType":"uint256","name":"part","type":"uint256"}],"name":"repay","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeTo","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISwapper","name":"swapper","type":"address"},{"internalType":"bool","name":"enable","type":"bool"}],"name":"setSwapper","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract ISwapper","name":"","type":"address"}],"name":"swappers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAsset","outputs":[{"internalType":"uint128","name":"elastic","type":"uint128"},{"internalType":"uint128","name":"base","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalBorrow","outputs":[{"internalType":"uint128","name":"elastic","type":"uint128"},{"internalType":"uint128","name":"base","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalCollateralShare","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"},{"internalType":"bool","name":"direct","type":"bool"},{"internalType":"bool","name":"renounce","type":"bool"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateExchangeRate","outputs":[{"internalType":"bool","name":"updated","type":"bool"},{"internalType":"uint256","name":"rate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userBorrowPart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userCollateralShare","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawFees","outputs":[],"stateMutability":"nonpayable","type":"function"}]