ERC-20
Overview
Max Total Supply
51,960.809449 ERC20 ***
Holders
24
Market
Onchain Market Cap
$0.00
Circulating Supply Market Cap
-
Other Info
Token Contract (WITH 6 Decimals)
Balance
842.279649 ERC20 ***Value
$0.00Loading...
Loading
Loading...
Loading
Loading...
Loading
# | Exchange | Pair | Price | 24H Volume | % Volume |
---|
Minimal Proxy Contract for 0x2cba6ab6574646badc84f0544d05059e57a5dc42
Contract Name:
KashiPairMediumRiskV1
Compiler Version
v0.6.12+commit.27d51765
Contract Source Code (Solidity)
/** *Submitted for verification at Etherscan.io on 2021-03-27 */ // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // 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/[email protected] // License-Identifier: MIT interface IFlashBorrower { function onFlashLoan( address sender, IERC20 token, uint256 amount, uint256 fee, bytes calldata data ) external; } // File @sushiswap/bentobox-sdk/contracts/[email protected] // 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/[email protected] // 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); } }
[{"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"}]
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.